import {
	getDeactivatedModules,
	getDefaultSchema,
	getSchema,
	getTemplate,
	hasProps,
	isEmptyContainer,
	isModuleDisabled,
	getRange,
	isValidDate,
	isValidDateRange,
	stringToDate,
	isEmptyObj,
	stripHtml,
	getFormTemplate,
	isComponentEmpty,
} from "@ax/helpers";

import { findByEditorID } from "@ax/forms";
import { IErrorItem, ISchemaField, ITemplate } from "@ax/types";
import { ERRORS } from "./errors";

const VALIDATORS = {
	mandatory: (val: string | any[], flag: boolean): IError => {
		const isValid = flag && val !== undefined && val !== null && val.length > 0;
		return { isValid, errorCode: "ERR001" };
	},
	maxChar: (val: string | null, max: number): IError => {
		const noHtmlVal = stripHtml(val);
		const isValid = noHtmlVal.length <= max;
		return { isValid, errorCode: "ERR003" };
	},
	minChar: (val: string | null, min: number): IError => {
		const noHtmlVal = stripHtml(val);
		const isValid = noHtmlVal.length >= min;
		return { isValid, errorCode: "ERR002" };
	},
	maxValue: (val: string, len: number): IError => {
		const isValid = parseFloat(val) <= len;
		return { isValid, errorCode: "ERR009" };
	},
	minValue: (val: string, len: number): IError => {
		const isValid = parseFloat(val) >= len;
		return { isValid, errorCode: "ERR008" };
	},
	format: (val: string, type: string): IError => {
		if (!val || (typeof val === "string" && val.trim().length === 0)) {
			return { isValid: true, errorCode: "" };
		}

		switch (type) {
			case "email": {
				const re = /^([\da-z_.-]+)@([\da-z.-]+)\.([a-z.]{2,6})$/;
				const isValid = re.test(String(val).toLowerCase());
				return { isValid, errorCode: "ERR004" };
			}
			case "fullURL": {
				const re = /^(https?:\/\/)?([\w.-]+)\.([a-z]{2,6})(:[0-9]{1,5})?(\/[^?]*)?(\?([^#]*))?(#(.*))?$/i;
				const isValid = re.test(val);
				return { isValid, errorCode: "ERR005" };
			}
			case "URL": {
				const re = /^(https?:\/\/)?([\w.-]+)\.([a-z]{2,6})(:[0-9]{1,5})?(\/[^?]*)?(\?([^#]*))?(#(.*))?$/i;
				const isValid = re.test(val);
				return { isValid, errorCode: "ERR006" };
			}
			case "phone": {
				const re =
					/\+?(9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)\d{1,14}$/;
				const isValid = re.test(val);
				return { isValid, errorCode: "ERR007" };
			}
			case "date": {
				const range = getRange(val);
				const isValid = range.length === 2 ? isValidDateRange(val) : isValidDate(val);
				return { isValid, errorCode: "ERR043" };
			}
			default:
				return { isValid: true, errorCode: "" };
		}
	},
	colorHex: (val: string): IError => {
		const re = /#[a-fA-F\d]{3}(?:[a-fA-F\d]{3})?\b/;
		const isValid = re.test(val);
		return { isValid, errorCode: "ERR034" };
	},
	dateFormat: (val: string, type: string): IError => {
		if (!val) {
			return { isValid: true, errorCode: "" };
		}
		const now = new Date();
		now.setHours(0, 0, 0, 0);
		const date = stringToDate(val);
		switch (type) {
			case "futureDate": {
				const isValid = isValidDate(val) && date >= now;
				return { isValid, errorCode: "ERR010" };
			}
			case "pastDate": {
				const isValid = isValidDate(val) && date <= now;
				return { isValid, errorCode: "ERR011" };
			}
			default:
				return { isValid: true, errorCode: "" };
		}
	},
	isMockup: (val: any, field: { type: string; defaultValue: any }): IError => {
		const isValid = !checkMockupByType(field.type, val, field.defaultValue);
		return { isValid, errorCode: "ERR016" };
	},
	apiValidator: (val: any, code: string): IError => {
		return { isValid: false, errorCode: code };
	},
	isSamePass: (pass1: string, pass2: string): IError => {
		const isValid = pass1 === pass2;
		return { isValid, errorCode: "ERR041" };
	},
	minItems: (val: any, len: number): IError => {
		const isValid = val.mode === "auto" || (val.fixed && val.fixed.length >= len);
		return { isValid, errorCode: "ERR019" };
	},
};

const getErrorMessage = (key: string, val: number | string | null): string => {
	const error: string = ERRORS[key];
	if (val) {
		return error.replace("{value}", val.toString());
	}
	return error;
};

const getValidationFunc = <T extends Record<string, unknown>, K extends keyof T>(key: K, obj: T): any => {
	return obj[key];
};

const getValidity = (validators: any, value: any): { isValid: boolean; errorText: string } => {
	let errorText = "";
	if (!validators) {
		return { isValid: true, errorText };
	}
	const isValidValue = Object.keys(validators)
		.map((validator: any) => {
			const validityVal = validators[validator];
			const validate = getValidationFunc(validator, VALIDATORS);
			const { isValid, errorCode } = validate(value, validityVal);
			if (!isValid) {
				errorText = getErrorMessage(errorCode, validityVal);
			}
			return isValid;
		})
		.every((el) => el === true);

	return { isValid: isValidValue, errorText };
};

const isEmptyField = (value: any, fieldType: string, multiple: boolean) => {
	switch (fieldType) {
		case "HeadingField":
			return !value || !value.content || value.content.trim().length === 0;
		case "ImageField":
			return !value || !value.url || value.url.trim().length === 0;
		case "UrlField":
			return !value || ((!value.href || value.href.trim().length === 0) && !value.linkTo);
		case "AsyncCheckGroup":
		case "CheckGroup":
		case "ComponentArray":
		case "FormFieldArray":
		case "MultiCheckSelect":
			return (
				!value || (Array.isArray(value) && !value.length) || (value.component === "Section" && !value.modules.length)
			);
		case "ArrayFieldGroup":
			return !value || !value.length;
		case "ReferenceField":
			return !value || (value.fixed && !value.fixed.length);
		case "ComponentContainer":
		case "FormContainer": {
			const { isEmpty } = isEmptyContainer(value, multiple);
			return isEmpty;
		}
		case "NumberField":
			return value === null || Number.isNaN(value);
		case "MultiCheckSelectGroup":
			return !value || isEmptyObj(value);
		default:
			return typeof value === "string" && value.trim().length === 0;
	}
};

const checkMockupByType = (type: string, value: any, defaultValue: any) => {
	if (!value || !defaultValue) return false;
	switch (type) {
		case "HeadingField":
			return value.content && defaultValue.content && value.content.trim() === defaultValue.content.trim();
		case "ImageField":
			return (
				(hasProps(value, ["publicId"]) && value.publicId === defaultValue.publicId) ||
				(hasProps(value, ["url"]) && value.url === defaultValue.url)
			);
		default:
			return typeof value === "string" && typeof defaultValue === "string" && value.trim() === defaultValue.trim();
	}
};

const checkMockupContent = (component: string, key: string, type: string, value: any) => {
	const moduleDefault = getDefaultSchema(component);
	return { isMockup: checkMockupByType(type, value, moduleDefault[key]), defaultValue: moduleDefault[key] };
};

const getValidationErrors = (
	fields: ISchemaField[],
	current: any,
	name: string | null,
	tab: string,
	template: boolean,
	isFormInPage: boolean,
) => {
	let errors: IErrorItem[] = [];

	const editorID = current?.editorID || 0;
	const component = current?.component || null;
	const parentEditorID = isFormInPage && current?.parentEditorID ? current.parentEditorID : undefined;

	fields.forEach((field) => {
		if (isFormInPage && !field.overwrite) return;

		if (field.mandatory) {
			const hasMultipleOptions = !!field.whiteList && field.whiteList.length > 1;

			const isEmpty =
				!current ||
				current[field.key] === undefined ||
				isEmptyField(current[field.key], field.type, hasMultipleOptions);

			if (isEmpty) {
				errors.push({
					type: "error",
					message: getErrorMessage("ERR015", null),
					validator: { mandatory: true },
					editorID,
					component,
					name: name ? name : field.title,
					key: field.key,
					tab,
					template,
					parentEditorID,
				});
			}
		}

		if (current && current.component && field.isMockup) {
			const { isMockup, defaultValue } = checkMockupContent(
				current.component,
				field.key,
				field.type,
				current[field.key],
			);

			if (isMockup) {
				errors.push({
					type: "error",
					message: getErrorMessage("ERR016", null),
					validator: { isMockup: { type: field.type, defaultValue } },
					editorID,
					component,
					name: name ? name : field.title,
					key: field.key,
					tab,
					template,
					parentEditorID,
				});
			}
		}

		let fieldValidators: Record<string, unknown> = field.maxValue ? { maxValue: field.maxValue } : {};
		fieldValidators = field.minValue ? { ...fieldValidators, minValue: field.minValue } : fieldValidators;
		fieldValidators = field.minItems ? { ...fieldValidators, minItems: field.minItems } : fieldValidators;

		if (hasProps(field, ["validators"]) || Object.keys(fieldValidators).length) {
			const allValidators = { ...field.validators, ...fieldValidators };

			const { isValid, errorText } = getValidity(allValidators, current[field.key]);

			if (!isValid) {
				errors.push({
					type: "error",
					message: errorText,
					validator: allValidators,
					editorID,
					component,
					name: name ? name : field.title,
					key: field.key,
					tab,
					template,
					parentEditorID,
				});
			}
		}

		if (
			Object.prototype.hasOwnProperty.call(field, "fields") &&
			field.fields?.length &&
			field.type !== "ArrayFieldGroup"
		) {
			let innerFields = field.fields;

			if (field.type === "ConditionalField") {
				innerFields = field.fields.filter((f) => f.condition === undefined || f.condition === current[field.key]);
				const hiddenFields = field.fields.filter((f) => f.condition !== current[field.key]);
				hiddenFields.forEach((field) => delete current[field.key]);
			}

			const innerErrors = getValidationErrors(innerFields, current, name, tab, template, isFormInPage);
			errors = [...errors, ...innerErrors];
		}

		if (field.type === "ArrayFieldGroup") {
			current &&
				current[field.key] &&
				current[field.key].forEach((item: any) => {
					const innerErrors =
						(field.fields && getValidationErrors(field.fields, item, name, tab, template, isFormInPage)) || [];
					errors = [...errors, ...innerErrors];
				});
		}

		if (field.type === "MultiCheckSelectGroup") {
			const multiCheckError = field.elements.some((item: any) =>
				item.mandatory &&
				(!current || !current[field.key] || !current[field.key][item.key] || !current[field.key][item.key].length)
					? true
					: false,
			);

			if (multiCheckError) {
				errors.push({
					type: "error",
					message: getErrorMessage("ERR015", null),
					validator: { mandatory: true },
					editorID,
					component,
					name: name ? name : field.title,
					key: field.key,
					tab,
					template,
					parentEditorID,
				});
			}
		}
	});

	return errors;
};

const isTemplateActivated = (templates: ITemplate[], currentTemplateType: string): boolean =>
	templates.find((temp: ITemplate) => temp.id === currentTemplateType) ? true : false;

const findPackagesActivationErrors = (
	pageEditor: any,
	modules: string[],
	templates: ITemplate[],
): IErrorItem | null => {
	const {
		schema,
		selectedContent: { component },
	} = pageEditor;

	const isGlobal = component === "GlobalPage";
	let deactivatedModules: string[] = [];
	let isCurrentTemplateActivated = true;
	let hasDeactivatedModules = false;

	const { template } = pageEditor?.editorContent || {};

	if (template && !isGlobal) {
		const mainContentModules = template?.mainContent?.modules;

		if (mainContentModules) {
			deactivatedModules = getDeactivatedModules(modules, mainContentModules);
			hasDeactivatedModules = deactivatedModules.length > 0;
		} else {
			hasDeactivatedModules = isModuleDisabled(component, schema.schemaType, modules);
		}

		isCurrentTemplateActivated = isTemplateActivated(templates, template.templateType);
	}

	if (!isCurrentTemplateActivated || hasDeactivatedModules) {
		return {
			type: "error",
			message: getErrorMessage("ERR042", null),
			validator: {},
			editorID: null,
			component: "",
			name: "",
			key: "",
			tab: "",
			template: false,
			hasDeactivatedPackage: true,
		};
	}

	return null;
};

const findFieldsErrors = (content: any, isForm?: boolean): IErrorItem[] => {
	const queue: any[] = [content];
	let errors: IErrorItem[] = [];
	while (queue.length > 0) {
		const currentObj = queue.shift();

		// TODO: revisar comprobación de componentes vacíos
		if (currentObj.component && !isComponentEmpty(currentObj)) {
			let schemaErrors: IErrorItem[] = [];
			const schema = getSchema(currentObj.component);
			schema &&
				schema.configTabs.forEach((tab) => {
					const valErrors = getValidationErrors(
						tab.fields,
						currentObj,
						schema.displayName || "",
						tab.title,
						false,
						false,
					);
					schemaErrors = [...schemaErrors, ...valErrors];
				});
			errors = [...errors, ...schemaErrors];
		}

		if (currentObj.type === "formTemplate" || currentObj.type === "template") {
			const isFormInPage = !isForm && currentObj.type === "formTemplate";
			const templateSchema: any =
				currentObj.type === "formTemplate"
					? getFormTemplate(currentObj.templateType)
					: getTemplate(currentObj.templateType);

			const { content } = templateSchema;

			const templateErrors: IErrorItem[] = getValidationErrors(
				content,
				currentObj,
				templateSchema.displayName,
				"content",
				isFormInPage ? false : true,
				isFormInPage,
			);

			errors = [...errors, ...templateErrors];
		}

		const keys = currentObj instanceof Object ? Object.keys(currentObj) : [];

		for (const key of keys) {
			const objVal = currentObj[key];
			if (objVal instanceof Object) {
				queue.push(objVal);
			}
		}
	}
	return errors;
};

const findMandatoryStructuredDataErrors = (content: any, schema: any): IErrorItem[] => {
	const errors: IErrorItem[] = getValidationErrors(schema.fields, content, null, "", false, false);
	return errors;
};

const checkH1content = (): IErrorItem | null => {
	const iframe = document.querySelector("iframe");
	const iframeContent = iframe?.contentWindow?.document;
	const h1s = iframeContent ? iframeContent.getElementsByTagName("h1") : null;

	if (h1s && !h1s.length) {
		return {
			type: "warning",
			message: getErrorMessage("ERR018", null),
			validator: {},
			editorID: null,
			component: null,
			name: "",
			key: "",
			tab: "",
			template: false,
		};
	}

	return null;
};

const parseValidationErrors = (errors: any[], content: any) => {
	return errors.map((err: any) => {
		const { element: module } = findByEditorID(content, err.editorID);
		const schema = getSchema(module.component);
		return {
			type: "error",
			message: getErrorMessage(err.error, null),
			validator: { apiValidator: err.error },
			editorID: err.editorID,
			component: module.component,
			name: schema?.displayName || "",
			key: err.key,
			tab: "content",
			template: false,
		};
	});
};

interface IError {
	isValid: boolean;
	errorCode: string;
}

export {
	getValidity,
	isTemplateActivated,
	findPackagesActivationErrors,
	findFieldsErrors,
	findMandatoryStructuredDataErrors,
	checkH1content,
	parseValidationErrors,
};
