import { AllowedTypes } from '../types/general';
import { ParsedValueResult } from '../types/records';
export class SQONValidation {
    lines: string[];
    position: number;
    validations: Record<string, any>;
    errors: Array<{ line: number; message: string }>;
    parsedSchema: Record<string, any>;
    validationKeywords: Record<string, string[]>;

    constructor({
        lines,
        position = 0,
        parsedSchema,
        validationKeywords,
    }: {
        lines: string[];
        position?: number;
        parsedSchema: Record<string, any>;
        validationKeywords: Record<string, string[]>;
    }) {
        this.lines = lines;
        this.position = position;
        this.validations = {};
        this.errors = [];
        this.parsedSchema = parsedSchema;
        this.validationKeywords = validationKeywords;
    }
    parseValidation(): { validations: Record<string, any>; errors: Array<{ line: number; message: string }> , position: number } {
        while (this.position < this.lines.length) {
            const line = this.lines[this.position].trim();
    
            if (line === "@end") {
                break;
            }

            if (line.startsWith('!#')) {
                this.position++;
                continue;
            }
    
            if (line.includes("->")) {
                this.processValidationLine(line);
            } else if (line !== '') {
                this.errors.push({ line: this.position + 1, message: `Invalid validation line: "${line}"` });
            }
    
            this.position++;
        }
    
        return { validations: this.validations, position: this.position, errors: this.errors };
    }    

    processValidationLine(line: string): void {
        if (!line.includes("->")) {
            this.errors.push({ line: this.position + 1, message: `Invalid format: ${line}` });
            return;
        }

        const [key, rulesStr] = line.split("->").map(s => s.trim());
        const rules = this.parseRules(rulesStr);

        this.validateRulesAgainstSchema(key, rules);

        if (!this.errors.some(err => err.line === this.position + 1)) {
            this.addValidation(key, rules);
        }
    }

    addValidation(key: string, rules: Record<string, any>): void {
        const parts = key.split('.');
        let current = this.validations;

        for (let i = 0; i < parts.length; i++) {
            const part = parts[i];
            const isLastPart = i === parts.length - 1;

            if (isLastPart) {
                if (!current[part]) {
                    current[part] = { rules: {} };
                }
                current[part].rules = { ...current[part].rules, ...rules };
            } else {
                if (!current[part]) {
                    current[part] = {};
                }
                current = current[part];
            }
        }
    }
    parseRules(rulesStr: string): Record<string, any> {
        const rules: Record<string, any> = {};

        rulesStr.split(';').forEach(rule => {
            const [ruleName, ruleValue] = rule.split('=').map(s => s.trim());
    
                if (ruleValue === 'true' || ruleValue === 'false') {
                    rules[ruleName] = ruleValue === 'true';
                } else if (ruleValue === 'null') {
                    rules[ruleName] = null;
                } else if (ruleValue === 'undefined') {
                    rules[ruleName] = undefined;
                } else if (/^\d+$/.test(ruleValue)) {
                    rules[ruleName] = parseInt(ruleValue, 10);
                } else if (/^\d+\.\d+$/.test(ruleValue)) {
                    rules[ruleName] = parseFloat(ruleValue);
                } else if (ruleValue.startsWith('"') && ruleValue.endsWith('"')) {
                    rules[ruleName] = ruleValue.slice(1, -1);
                } else if (ruleValue.startsWith('[') && ruleValue.endsWith(']')) {
                    const arrayContent = ruleValue.slice(1, -1).trim();
                    rules[ruleName] = arrayContent
                        ? this.parseArray(arrayContent).map(item => this.parseValue(item))
                        : [];
                } else if (ruleValue.startsWith('{') && ruleValue.endsWith('}')) {
                    const objectContent = ruleValue.slice(1, -1).trim();
                    rules[ruleName] = objectContent
                        ? Object.fromEntries(
                              Object.entries(this.parseObject(objectContent)).map(([key, value]) => [
                                  key,
                                  this.parseValue(value),
                              ])
                          )
                        : {};
                } else {
                    this.errors.push({ 
                        line: this.position + 1, 
                        message: `Invalid format for value of '${ruleName}' in '${rule}'`
                    });
                }
        });
    
        return rules;
    }
            

    parseArray(content: string): string[] {
        const result: string[] = [];
        const items = content.split(',');
        for (let i = 0; i < items.length; i++) {
          result.push(items[i].trim());
        }
        return result;
      }
    
    parseObject(content: string): Record<string, any> {
        const obj: Record<string, any> = {};
        const pairs = content.split(',').map(item => item.trim());
        
        pairs.forEach(pair => {
            const [key, value] = pair.split(':').map(p => p.trim());
            const parsedKey = key.startsWith('"') && key.endsWith('"') ? key.slice(1, -1) : key;
            obj[parsedKey] = this.parseValue(value);
        });
        return obj;
    }


    validateRulesAgainstSchema(key: string, rules: Record<string, any>): void {
        const schemaTypeArray = this.getSchemaType(key);
    
        if (!schemaTypeArray) {
            this.errors.push({ 
                line: this.position + 1, 
                message: `Schema type not found for '${key}'`
            });
            return;
        }
    
        Object.keys(rules).forEach(ruleName => {
            const validTypes = this.validationKeywords[ruleName];
            const ruleValue = rules[ruleName];
    
            if (!validTypes) {
                this.errors.push({
                    line: this.position + 1,
                    message: `Validation rule '${ruleName}' is not defined.`
                });
                return;
            }
    
            const isApplicable = validTypes.includes('Any') || schemaTypeArray.some(type => 
                validTypes.includes(type as AllowedTypes));
            

            if (!isApplicable) {
                this.errors.push({
                    line: this.position + 1,
                    message: `Validation '${ruleName}' is not applicable to types for key '${key}'`
                });
            }
    
            if (ruleName === 'enum' || ruleName === 'hasProperties') {
                const isValueValid = schemaTypeArray.some(type => {
            
                    if ((type === 'ObjectArray' || type === 'Object[]' || type === 'Object') && Array.isArray(ruleValue)) {
                        const extractedValues = ruleValue.map((item: any) => item.value);
                        return extractedValues.every(value => ruleValue.some((allowedValue: any) => allowedValue.value === value)) || ruleValue.length === 0;
                    }
                    if (type === 'NumberArray' || type === 'Number[]' && Array.isArray(ruleValue)) {
                        const extractedValues = ruleValue.map((item: any) => item.value);                        return extractedValues.every((value: number[]) => ruleValue.some((allowedValue: any) => allowedValue.value === value)) || ruleValue.length === 0;
                    }
                    if (type === 'StringArray' || type === 'String[]' && Array.isArray(ruleValue)) {
                        const extractedValues = ruleValue.map((item: any) => item.value);
                        return extractedValues.every((value: string[]) => ruleValue.includes(value));
                    }
            
                    return false;
                });
            
                if (!isValueValid) {
                    this.errors.push({
                        line: this.position + 1,
                        message: `Invalid value for '${ruleName}' in '${key}'.`
                    });
                }
            }
            
    
            if (schemaTypeArray.includes('ObjectArray')) {
                this.validateObjectArrayItems(key, rules);
            } else if (schemaTypeArray.includes('Object')) {
                this.validateObjectProperties(key, rules);
            }
        });
    }
        
     
    validateObjectArrayItems(key: string, rules: Record<string, any>): void {
        const parts = key.split('.');
        const arrayKey = parts[parts.length - 2];
        const schemaItem = this.parsedSchema[arrayKey]?.items;
    
        if (!schemaItem) return;
    
        Object.keys(rules).forEach(ruleName => {
            const validTypes = this.validationKeywords[ruleName];
    
            if (validTypes && !validTypes.includes('Object')) {
                this.errors.push({
                    line: this.position + 1,
                    message: `Validation '${ruleName}' is not applicable to type 'ObjectArray' items for key '${key}'`
                });
            }
        });
    }
    
    validateObjectProperties(key: string, rules: Record<string, any>): void {
        const parts = key.split('.');
        const objectKey = parts[parts.length - 2];
        const schemaProperties = this.parsedSchema[objectKey]?.properties;
    
        if (!schemaProperties) return;
    
        Object.keys(rules).forEach(ruleName => {
            const validTypes = this.validationKeywords[ruleName];
    
            const propSchema = schemaProperties[parts[parts.length - 1]];
            if (propSchema && validTypes) {
                const propTypes = propSchema.type;
    
                const isValid = propTypes.some((type: any) => validTypes.includes(type) || validTypes.includes('Any'));
                if (!isValid) {
                    this.errors.push({
                        line: this.position + 1,
                        message: `Validation '${ruleName}' is not applicable to property '${parts[parts.length - 1]}' of Object for key '${key}'`
                    });
                }
            }
        });
    }


    parseRuleValue(value: string): any {
        if (value === "true") return true;
        if (value === "false") return false;
        if (!isNaN(Number(value))) return Number(value);
        return value;
    }
    

    getSchemaType(key: string): string[] {
        const schema = this.parsedSchema;
        const parts = key.split('.');
        let current = schema;
    
        const processPart = (current: any, parts: string[], index: number): string[] => {
            const part = parts[index];
            const isLastPart = index === parts.length - 1;
    
            if (isLastPart) {
                if (current[part]) {
                    return Array.isArray(current[part].type) ? current[part].type : [current[part].type];
                } else {
                    return [];
                }
            }
    
            if (current[part]) {
                if (Array.isArray(current[part].type) && current[part].type.includes("Object") && current[part].properties) {
                    return processPart(current[part].properties, parts, index + 1);
                } else if (Array.isArray(current[part].type) && current[part].type.includes("ObjectArray") && current[part].items) {
                    return processPart(current[part].items, parts, index + 1);
                } else {
                    return [];
                }
            } else {
                return [];
            }
        };
    
        return processPart(current, parts, 0);
    }

    

    parseValue(value: string): ParsedValueResult {
        let valueToStore: any;
        let type: string;
    
        if (value === "") {
            valueToStore = undefined;
            type = 'undefined';
        } else if (value.startsWith('"') && value.endsWith('"')) {
            valueToStore = value.slice(1, -1);
            type = valueToStore === "" ? 'undefined' : 'String';
            valueToStore = valueToStore === "" ? undefined : valueToStore;
        } else if (value.startsWith('<Buffer') && value.endsWith('>')) {
            valueToStore = value;
            type = 'Binary';
        } else if (value.startsWith('Uint8Array[') && value.endsWith(']')) {
            valueToStore = value;
            type = 'Uint8Array';
        } else if (!isNaN(Number(value)) && !isNaN(parseFloat(value)) && !value.startsWith('0x')) {
            const numberValue = parseFloat(value);
            if (numberValue > Number.MAX_SAFE_INTEGER || numberValue < Number.MIN_SAFE_INTEGER) {
                valueToStore = BigInt(value);
                type = 'Number';
            } else {
                valueToStore = numberValue;
                type = 'Number';
            }
        } else if (value === 'TRUE') {
            valueToStore = true;
            type = 'Boolean';
        } else if (value === 'FALSE') {
            valueToStore = false;
            type = 'Boolean';
        } else if (value === 'NULL') {
            valueToStore = null;
            type = 'Null';
        } else if (value === 'undefined') {
            valueToStore = undefined;
            type = 'undefined';
        } else if (this.isValidDate(value)) {
            valueToStore = this.parseDate(value);
            type = 'Date';
            if (!valueToStore) {
                return { error: `Invalid date format: '${value}'.`, type: 'error' };
            }
        } else {
            return { error: `Invalid value format: '${value}'.`, type: 'error' };
        }
    
        return { value: valueToStore, type };
    }
    
        
isValidDate(value: string): boolean {
    const dateFormats = [
        /^\d{1,2}(st|nd|rd|th)?\s+\w+\s+\d{4}$/i,
        /^\d{1,2}[/-]\d{1,2}[/-]\d{2,4}$/,
        /^\d{4}[/-]\d{1,2}[/-]\d{1,2}$/,
        /^\d{1,2}:\d{2}:\d{2}([AP]M)?$/,
        /^\d{1,2}:\d{2}([AP]M)?$/,
        /^\d{10}$/,
        /^\d{13}$/,
        /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|([+-]\d{2}:\d{2}))?$/
    ];

    const matchesRegex = dateFormats.some(format => format.test(value));
    if (matchesRegex) {
        return true;
    }

    const parsedDate = new Date(value);
    return !isNaN(parsedDate.getTime());
}

    parseDate(value: string): Date | null {
        const dayMonthYear = value.match(/^(\d{1,2})(st|nd|rd|th)?\s+(\w+)\s+(\d{4})$/);
        if (dayMonthYear) {
            const day = parseInt(dayMonthYear[1], 10);
            const month = new Date(`${dayMonthYear[3]} 1`).getMonth();
            const year = parseInt(dayMonthYear[4], 10);
            return new Date(year, month, day);
        }

        if (!isNaN(Date.parse(value))) {
            return new Date(value);
        }

        return null;
    }
}
