import {WebApi} from './WebApi';

export class Service {
    public static async parseCreateModel(entityLogicalName: string, model: Model): Promise<Model> {
        const entity = Service.parseModel(model),
            metadata = await Xrm.Utility.getEntityMetadata(entityLogicalName, Object.keys(entity));
        Service.cleanCloneModel(entity, metadata);
        return WebApi.populateBindings(entity, metadata);
    }

    private static cleanCloneModel(model: Model, metadata: Xrm.Metadata.EntityMetadata): void {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        delete (model as any)[metadata.PrimaryIdAttribute];
        delete model.statuscode;
        delete model.statecode;

        delete model.ownerid;
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        delete (model as any)['_ownerid_value@Microsoft.Dynamics.CRM.lookuplogicalname'];
        // delete model.modifiedon;
        // delete model.createdon;
        // delete model.versionnumber;
    }

    private static parseModel(model: Model): Model {
        const keys = Object.keys(model),
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            cleanModel: any = {...model};
        for (const key of keys) {
            if (cleanModel[key] === null) {
                delete cleanModel[key];
            } else if (key.startsWith('@')) {
                delete cleanModel[key];
            } else if (key.endsWith('FormattedValue')) {
                delete cleanModel[key];
            } else if (key.startsWith('_') && key.endsWith('_value') && !key.includes('@')) {
                const navigationProperty = cleanModel[`${key}@Microsoft.Dynamics.CRM.associatednavigationproperty`],
                    trimmedKey = key.substr(1, key.length - 7);
                if (navigationProperty) {
                    cleanModel[trimmedKey] = cleanModel[key];
                    delete cleanModel[`${key}@Microsoft.Dynamics.CRM.associatednavigationproperty`];
                } else {
                    delete cleanModel[`${key}@Microsoft.Dynamics.CRM.lookuplogicalname`];
                }
                delete cleanModel[key];
                delete cleanModel[`${trimmedKey}_LogicalName`]; // Added in WebApi#parseValues
            }
        }
        return cleanModel;
    }

    public static async validateRecord(entityLogicalName: string, model: Model): Promise<ModelValidation> {
        let validation = await Service.validateRequiredAttributes(entityLogicalName, model);
        if (validation.isValid) {
            validation = await Service.validateMesh(entityLogicalName, model);
        }
        return validation;
    }

    private static async validateMesh(entityLogicalName: string, model: Model): Promise<ModelValidation> {
        const keys = Object.keys(model),
            wrongKey = keys.find(key => key.startsWith('_') || key.endsWith('FormattedValue') || key.endsWith('LogicalName'));
        if (wrongKey) {
            return {
                isValid: false,
                attribute: wrongKey,
                category: 'Invalid key'
            };
        }
        return {isValid: true};
    }

    private static async validateRequiredAttributes(entityLogicalName: string, model: Model): Promise<ModelValidation> {
        const requiredAttributeLogicalNames = await Service.getRequiredAttributeLogicalNames(entityLogicalName),
            filteredAttributeMetadatas = await Xrm.Utility.getEntityMetadata(entityLogicalName, requiredAttributeLogicalNames),
            keys = Object.keys(model);
        for (const name of requiredAttributeLogicalNames) {
            const attributeDescriptor = Service.getAttributeDescriptor(name, filteredAttributeMetadatas),
                property = attributeDescriptor.LogicalName,
                bindings: string[] = [];
            const targets: string[] = attributeDescriptor.Targets || [];
            for (const target of targets) {
                const binding = await WebApi.getBinding(property, 'dummy', filteredAttributeMetadatas, target);
                bindings.push(Object.keys(binding)[0]);
            }
            if (!keys.includes(property) && !keys.includes(`${property}@odata.bind`) && !bindings.some(binding => keys.includes(binding))) {
                return {
                    isValid: false,
                    attribute: name,
                    category: 'Mandatory'
                };
            }
        }
        return {isValid: true};
    }

    private static getAttributeDescriptor(attribute: string, entityMetadata: Xrm.Metadata.EntityMetadata): {
        AttributeOf?: string;
        LogicalName: string;
        Targets: string[];
    } {
        const attributeMetadata = entityMetadata.Attributes.get(attribute);
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-ignore
        let attributeDescriptor = attributeMetadata.attributeDescriptor;
        if (attributeDescriptor.AttributeOf) {
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @ts-ignore
            attributeDescriptor = entityMetadata.Attributes.get(attributeDescriptor.AttributeOf).attributeDescriptor;
        }
        return attributeDescriptor;
    }

    private static async getRequiredAttributeLogicalNames(entityLogicalName: string): Promise<string[]> {
        const rawAttributesMetadatas = await WebApi.getAttributesMetadata(entityLogicalName, ['LogicalName', 'RequiredLevel']);
        return rawAttributesMetadatas
            .filter((attrMetadata: {RequiredLevel: {Value: string}}) => attrMetadata.RequiredLevel.Value === 'ApplicationRequired')
            .map((requiredAttributeMetadata: {LogicalName: string}) => requiredAttributeMetadata.LogicalName);
    }
}