import {isObjectType} from "graphql";
import {logger} from "../logging/logger.js";
import {Device, Host} from "../schema/generated/graphql.js";

export const isDevice = (value: Host | undefined): value is Device => !!(value as Device)?.deviceType;
/*
    As a default all . - seperators within a key shall be replaced by a Capital letter of the following word
 */
function defaultKeyMappingFunction(key: string): string {
    let words = key.split(".")
    for (let i = 1; i < words.length; i++) {
        if (words[i]) {
            words[i] = words[i][0].toUpperCase() + words[i].substring(1);
        }
    }
    return words.join("")
}

/**
 * Creates a resolver for an object type where each field is resolved by mapping Zabbix items or tags
 * to a hierarchical structure based on field names or Zabbix keys.
 *
 * This function iterates over all fields of the specified GraphQL type and assigns a resolver
 * that uses the provided `sourceFieldMapper` to retrieve the value. It automatically detects
 * whether a field is a scalar or an object type to guide the mapping process.
 *
 * @param schema - The executable GraphQL schema.
 * @param typename - The name of the GraphQL type for which to create the resolver.
 * @param sourceFieldMapper - A function that maps a field name to its value from the parent object.
 *                            It receives the field name, the parent object, and a boolean indicating
 *                            if the requested field is an object type.
 * @returns A resolver object containing field-to-function mappings for the specified type.
 */
export function createHierarchicalValueFieldResolver(
    schema: any, typename: string,
    sourceFieldMapper: (fieldname: string, parent: any, objectTypeRequested: boolean) => { [p: string]: any } | null): {
    [fieldname: string]: any
} {
    let resolver: { [fieldname: string]: any } = {}

    let type = schema.getType(typename)
    if (isObjectType(type)) {
        let fields = type.getFields();
        for (let fieldsKey in fields) {
            let field = fields[fieldsKey];
            resolver[field.name] = (parent: any) => sourceFieldMapper(field.name, parent, isObjectType(field.type));
        }
    }
    return resolver
}

export function zabbixItemValueSourceFieldMapper(
    fieldname: string,
    parent: {
        items: [{ itemid: string, key_: string; name: string, lastvalue: any }],
        [key: string]: any
    },
    objectTypeRequested: boolean
) {
    let result: { [p: string]: any; } | any = parent[fieldname]
    if (!parent.items) {
        logger.debug(`No parent.items found: ${JSON.stringify(parent)}`)
        return result
    }
    parent.items.forEach(
        item => {
            result = mapAttributeListToGraphQlType(result, objectTypeRequested, fieldname, {
                    key: item.key_,
                    value: item.lastvalue
                }
            )
        }
    )
    logger.info(`Device data mapped:  ${JSON.stringify(result)}`)

    return result;
}

export function zabbixTagsValueSourceFieldMapper(
    fieldname: string,
    tags:  [{ tag: string, value: any }],
    objectTypeRequested: boolean
) {
    let result: { [p: string]: any; } | any = {}
    if (!tags) {
        logger.debug(`No parent.tags or parent.inheritedTags found: ${JSON.stringify(tags)}`)
        return result
    }
    tags.forEach(
        tag => {
            result = mapAttributeListToGraphQlType(result, objectTypeRequested, fieldname, {
                    key: tag.tag,
                    value: tag.value
                }
            )
        }
    )
    logger.info(`Device tags mapped:  ${JSON.stringify(result)}`)

    return result;
}

function mapAttributeListToGraphQlType(result: {
    [p: string]: any;
} | any, objectTypeRequested: boolean, fieldname: string, item: { value: any, key: string }) {

    logger.debug(`Resolving ${objectTypeRequested ? "attributes of object" : "value of scalar"} field parent.${fieldname} (${result}), looking up key from item ${JSON.stringify(item)}`)
    if (item.key) {
        if (objectTypeRequested) {
            function addRecursive(
                result: { [x: string]: any; } | null,
                fieldHierarchy: string[],
                value: any
            ) {
                if (!fieldHierarchy || fieldHierarchy.length == 0) {
                    return result

                } else {
                    if (!result) {
                        result = {}
                    }
                    if (fieldHierarchy.length == 1) {
                        let fieldTokenName = fieldHierarchy[0];
                        const TOKEN_SEPERATOR = "_";

                        // As value is not typed we must parse the type in order to transform it to a strongly
                        // typed value which is expected by  Graphql
                        // Example: Graphql does not accept a string "true" and empty string as false as boolean
                        // In order to facilitate this it is possible (but not mandatory) to provide typehints
                        // to item keys by prepending the fieldTokenName with a typehint, following by an underscore.
                        // I.e. if a key is prefixed with str_, bool_, float_ or json_ this will be stripped
                        // and the value will be cast to the appropriate type.
                        // If no typeHint is provided and the type is string it will be tried to create a float
                        // or a boolean out of it.
                        let typeHintToken = fieldTokenName.split(TOKEN_SEPERATOR);
                        let typeHint = undefined;
                        if (typeHintToken.length > 0) {
                            switch (typeHintToken[0]) {
                                case "str":
                                case "bool":
                                case "float":
                                case "json":
                                    typeHint = typeHintToken[0];
                                    // Remove typehint + token separator from field name - if the typehint
                                    // is followed by another token. If not (e.g. fieldTokenName="str" only) the
                                    // token is considered to be a valid typehint, but it is not stripped from the
                                    // fieldTokenName (i.e. nothing happens to the fieldTokenName in that case)
                                    if (typeHintToken.length > 1) {
                                        fieldTokenName = fieldTokenName.substring(typeHint.length + 1);
                                    }
                            }
                        }
                        let fieldValue = undefined;

                        if (typeof value === 'string' && (typeHint == "bool" || value.toLowerCase() === 'true' || value.toLowerCase() === 'false')) {
                            fieldValue = (value.toLowerCase() === 'true');
                            // logger.debug("Parsing attribute '" + fieldTokenName + "' as true/false string, type=" + typeof fieldValue + ", value=" + fieldValue);
                        } else if (typeof value === 'string' && value !== '' && !isNaN(Number(value))) {
                            fieldValue = Number(value);
                            // logger.debug("Parsing attribute '" + fieldTokenName + "' as number, type=" + typeof fieldValue + ", value=" + fieldValue);
                        } else if (typeof value === 'string' && typeHint == "json") {
                            logger.debug("Trying to parse attribute value as json, typeHint=" + typeHint + ", type=" + typeof value + ", value=" + value);
                            if (!value) {
                                // Empty string values will be considered to be an unset JSON-Object if there is a typeHin=="json"
                                fieldValue = undefined;
                            } else {
                                try {
                                    fieldValue = JSON.parse(value);
                                    // logger.debug("Parsing attribute '" + fieldTokenName + "' as json, type=" + typeof fieldValue + ", value=" + value);
                                } catch (e) {
                                    logger.debug("Unable to parse attribute value as json, passing unmodified, type=" + typeof value + ", value=" + value);
                                    fieldValue = value;
                                }
                            }
                        } else {
                            fieldValue = value;
                            // logger.debug("Passing attribute '" + fieldTokenName + "' unmodified, type=" + typeof fieldValue + ", value=" + fieldValue);
                        }
                        if (fieldValue !== undefined) {
                            // logger.debug(`length of parsed field hierarchy is 1: Setting fieldTokenName=${fieldTokenName} to ${value}`);
                            result[fieldTokenName] = fieldValue;
                        } /*else {
                            // logger.debug(`length of parsed field hierarchy is 1: Skipping to set fieldHierarchy=${fieldHierarchy} to ${value} (empty value)`);
                        }*/
                    } else {
                        result[fieldHierarchy[0]] = addRecursive(result[fieldHierarchy[0]], fieldHierarchy.slice(1), value)
                    }
                }
                logger.debug(`Adding attribute ${fieldHierarchy[0]}, result:  ${JSON.stringify(result)}`)

                return result
            }

            let fieldHierarchy = item.key.split(".");
            if (fieldHierarchy[0] == fieldname) {
                result = addRecursive(result, fieldHierarchy.slice(1), item.value);
                logger.debug(`Detected matching item key ${fieldname} in item , result:  ${JSON.stringify(result)}`)
            } else {
                logger.debug(`Item key ${fieldHierarchy[0]} not matched fieldname=${fieldname}, result:  ${JSON.stringify(result)}`)
            }

        } else {
            let keyInCamel = defaultKeyMappingFunction(item.key);
            if (keyInCamel == fieldname) {
                result = item.value
                logger.debug(`Detected matching item key ${keyInCamel} in item , result:  ${JSON.stringify(result)}`)
            }
        }
    }
    return result;
}
