import Joi, { StringSchema, NumberSchema, DateSchema, ObjectSchema, Schema, AnySchema } from 'joi';
import { I18N } from './i18n';
import { ErrorHandler } from "./errors"

export class JoiBuilder {
    static buildJoiSchema = (definition: SchemaDefinition, options: { allowUnknown?: boolean, _oxorGroups?: string[][] } = {}): Joi.ObjectSchema => {
        const schema: Record<string, Schema> = {};
        for (const [key, value] of Object.entries(definition)) {
            const field = { ...value } as Field;
            if (field && field.type) {
                let joiField: Schema;
                let originalErrorMessage = field.errorMessage;
                field.errorMessage = field.errorMessage ? I18N.t(field.errorMessage, { language: process.env.DEFAULT_LANGUAGE_CODE }) : field.errorMessage;
                field.description = field.description ? I18N.t(field.description, { language: process.env.DEFAULT_LANGUAGE_CODE }) : field.description
                if (field.type === 'oneOf' && field.allow) {
                    // map allowed types into Joi schemas
                    const schemas = field.allow.map((t: string) => {
                        switch (t) {
                            case "string":
                                return Joi.string();
                            case "number":
                                return Joi.number();
                            case "boolean":
                                return Joi.boolean();
                            case "date":
                                return Joi.date();
                            default:
                                throw new Error(`Unsupported type: ${t}`);
                        }
                    });
                    joiField = Joi.alternatives().try(...schemas);
                } else if (field.type === 'string') {
                    let stringField: StringSchema = Joi.string().trim();
                    if (field.allowEmpty) stringField = stringField.allow('');
                    if (field.min !== undefined) stringField = stringField.min(field.min);
                    if (field.max !== undefined) stringField = stringField.max(field.max);
                    if (field.valid !== undefined) stringField = stringField.valid(...field.valid);
                    joiField = stringField;
                } else if (field.type === 'email') {
                    let stringField: StringSchema = Joi.string().trim().email();
                    if (field.allowEmpty) stringField = stringField.allow('');
                    if (field.min !== undefined) stringField = stringField.min(field.min);
                    if (field.max !== undefined) stringField = stringField.max(field.max);
                    joiField = stringField;
                } else if (field.type === 'number') {
                    let numberField: NumberSchema = Joi.number();
                    if (field.min !== undefined) numberField = numberField.min(field.min);
                    if (field.max !== undefined) numberField = numberField.max(field.max);
                    if (field.valid !== undefined) numberField = numberField.valid(...field.valid);
                    if (field.precision !== undefined) numberField = numberField.precision(field.precision);
                    joiField = numberField;
                } else if (field.type === 'date') {
                    joiField = Joi.date();
                } else if (field.type === 'time') {
                    joiField = Joi.string().trim().pattern(/^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/);
                } else if (field.type === 'file') {
                    joiField = Joi.any().meta({ swaggerType: 'file' });
                } else if (field.type === 'object') {
                    if ((field as ObjectField).fields) {
                        joiField = this.buildJoiSchema((field as ObjectField).fields, { allowUnknown: (field as any).allowUnknown }).label(field.label ?? 'label-required').description(field.description ?? 'description-required');
                    } else {
                        joiField = Joi.object();
                        //console.dir(joiField.describe(), { depth: null })
                    }
                } else if (field.type === 'array') {
                    const arrayField = field as ArrayField;
                    // Build item schema: can be primitive or nested object
                    let itemSchema: Schema;
                    if (arrayField && arrayField.items) {
                        if (arrayField.items.type === 'object') {
                            itemSchema = this.buildJoiSchema((arrayField.items as ObjectField).fields, { allowUnknown: (arrayField.items as any).allowUnknown })
                                .label(arrayField.items.label ?? 'Item')
                                .description(arrayField.items.description ?? 'item-description-required');
                        } else {
                            // Reuse existing field building logic recursively
                            itemSchema = this.buildJoiSchema({ item: arrayField.items }).extract('item');
                        }
                        let joiArray = itemSchema ? (arrayField.items.allow?Joi.array().items(itemSchema):Joi.array().items(itemSchema).single()) : Joi.array();
                        if (arrayField.minItems !== undefined) joiArray = joiArray.min(arrayField.minItems);
                        if (arrayField.maxItems !== undefined) joiArray = joiArray.max(arrayField.maxItems);
                        if (arrayField.unique) joiArray = joiArray.unique();
                        joiField = joiArray.label(field.label ?? 'label-required').description(field.description ?? 'description-required');;
                    } else {
                        joiField = Joi.array().label(field.label ?? 'label-required').description(field.description ?? 'description-required');;
                    }
                } else if (field.type === 'boolean') {
                    joiField = Joi.boolean();
                } else if (field.type === 'any') {

                    // joiField = Joi.alternatives().try(
                    //     Joi.string(),
                    //     Joi.number(),
                    //     Joi.boolean()
                    // )
                    joiField = Joi.any()
                }
                else {
                    throw new Error(`Unsupported type: ${(field as any).type}`);
                }
                if (field.pattern) {
                    joiField = Joi.string().trim().pattern(field.pattern)
                }
                if (field.defaultValue !== undefined) joiField = joiField.default(field.defaultValue);
                if (field.allowNull) joiField = joiField.allow(null);
                if (field.description) joiField = joiField.description(field.description);
                if (field.example !== undefined) joiField = joiField.example(field.example);
                if (field.optionalIf) {
                    const depField = field.optionalIf.field;
                    const depValues = field.optionalIf.values;

                    const condition = depValues?.length
                        ? Joi.valid(...depValues)
                        : Joi.exist().not(null);

                    joiField = joiField.when(depField, {
                        is: condition,
                        then: joiField.optional().error(errors => ErrorHandler.routeError(errors, originalErrorMessage ?? 'VALIDATION_FAILED')),
                        otherwise: field.required
                            ? joiField.required().error(errors => ErrorHandler.routeError(errors, originalErrorMessage ?? 'VALIDATION_FAILED'))
                            : joiField.optional().error(errors => ErrorHandler.routeError(errors, originalErrorMessage ?? 'VALIDATION_FAILED')),
                    });
                } else if (field.optionalIfAny && field.optionalIfAny.length > 0) {
                    for (const dep of field.optionalIfAny) {
                        const depField = typeof dep === 'string' ? dep : dep.field;
                        const depCondition = typeof dep === 'string'
                            ? Joi.exist().not(null)
                            : dep.values?.length
                                ? Joi.valid(...dep.values)
                                : Joi.exist().not(null);

                        joiField = joiField.when(depField, {
                            is: depCondition,
                            then: joiField.optional().error(errors => ErrorHandler.routeError(errors, originalErrorMessage ?? 'VALIDATION_FAILED')),
                        });
                    }

                    joiField = field.required
                        ? joiField.required().error(errors =>
                            ErrorHandler.routeError(errors, originalErrorMessage ?? 'VALIDATION_FAILED'))
                        : joiField.optional().error(errors => ErrorHandler.routeError(errors, originalErrorMessage ?? 'VALIDATION_FAILED'));
                }
                if (field.forbiddenIf) {
                    const depField = field.forbiddenIf.field;
                    const depValues = field.forbiddenIf.values;

                    const condition = depValues?.length
                        ? Joi.valid(...depValues)
                        : Joi.exist().not(null);

                    joiField = joiField.when(depField, {
                        is: condition,
                        then: joiField.forbidden().error(errors => ErrorHandler.routeError(errors, 'FIELD_IS_NOT_ALLOWED')),
                        otherwise: field.required
                            ? joiField.required().error(errors => ErrorHandler.routeError(errors, originalErrorMessage ?? 'VALIDATION_FAILED'))
                            : joiField.optional().error(errors => ErrorHandler.routeError(errors, originalErrorMessage ?? 'VALIDATION_FAILED')),
                    });
                }
                // else if (field.forbiddenIfAny && field.forbiddenIfAny.length > 0) {
                //     for (const dep of field.forbiddenIfAny) {
                //         const depField = typeof dep === 'string' ? dep : dep.field;
                //         const depCondition = typeof dep === 'string'
                //             ? Joi.exist().not(null)
                //             : dep.values?.length
                //                 ? Joi.valid(...dep.values)
                //                 : Joi.exist().not(null);

                //         joiField = joiField.when(depField, {
                //             is: depCondition,
                //             then: Joi.forbidden().error(errors => ErrorHandler.routeError(errors, 'FIELD_IS_NOT_ALLOWED')),
                //         });
                //     }
                //     joiField = field.required
                //         ? joiField.required().error(errors =>
                //             ErrorHandler.routeError(errors, originalErrorMessage ?? 'VALIDATION_FAILED'))
                //         : joiField.optional()
                //     // console.log(key)
                // if(field.type === 'oneOf') 
                //         console.dir(joiField.describe(), { depth: null })
                // } 
                if (field.forbiddenIfAny && field.forbiddenIfAny.length > 0) {
                    const group = [key, ...field.forbiddenIfAny.map(dep =>
                        typeof dep === 'string' ? dep : dep.field
                    )];
                    if (!options._oxorGroups) options._oxorGroups = [];
                    options._oxorGroups.push(group);
                    joiField = joiField.optional();
                }
                else {
                    if (field.required) {
                        joiField = joiField.required().error(errors => { return ErrorHandler.routeError(errors, originalErrorMessage ? originalErrorMessage : "VALIDATION_FAILED") });
                    } else {
                        joiField = joiField.optional().error((errors) => { return ErrorHandler.routeError(errors, originalErrorMessage ?? 'VALIDATION_FAILED') });
                    }
                }
                if (field.required == false) {
                    joiField.optional().error((errors) => { return ErrorHandler.routeError(errors, originalErrorMessage ? originalErrorMessage : "VALIDATION_FAILED") });
                }
                schema[key] = joiField;
            }
        }

        let objectSchema = Joi.object(schema);
        if (options._oxorGroups) {
            for (const group of options._oxorGroups) {
                if (group.length > 1) {
                    objectSchema = objectSchema.oxor(...group);
                }
            }
        }
        return options.allowUnknown ? objectSchema.unknown(true) : objectSchema;
    }
}