import * as plugins from '../plugins.js';
import { ValidationError } from '../errors/base.errors.js';

/**
 * Validation result
 */
export interface IValidationResult {
  /**
   * Whether the validation passed
   */
  valid: boolean;
  
  /**
   * Validation errors if any
   */
  errors?: string[];
  
  /**
   * Validated configuration (may include defaults)
   */
  config?: any;
}

/**
 * Validation schema types
 */
export type ValidationSchema = Record<string, {
  /**
   * Type of the value
   */
  type: 'string' | 'number' | 'boolean' | 'object' | 'array';
  
  /**
   * Whether the field is required
   */
  required?: boolean;
  
  /**
   * Default value if not specified
   */
  default?: any;
  
  /**
   * Minimum value (for numbers)
   */
  min?: number;
  
  /**
   * Maximum value (for numbers)
   */
  max?: number;
  
  /**
   * Minimum length (for strings or arrays)
   */
  minLength?: number;
  
  /**
   * Maximum length (for strings or arrays)
   */
  maxLength?: number;
  
  /**
   * Pattern to match (for strings)
   */
  pattern?: RegExp;
  
  /**
   * Allowed values (for strings, numbers)
   */
  enum?: any[];
  
  /**
   * Nested schema (for objects)
   */
  schema?: ValidationSchema;
  
  /**
   * Item schema (for arrays)
   */
  items?: {
    type: 'string' | 'number' | 'boolean' | 'object';
    schema?: ValidationSchema;
  };
  
  /**
   * Custom validation function
   */
  validate?: (value: any) => boolean | string;
}>;

/**
 * Configuration validator
 * Validates configuration objects against schemas and provides default values
 */
export class ConfigValidator {

  /**
   * Validate a configuration object against a schema
   * 
   * @param config Configuration object to validate
   * @param schema Validation schema
   * @returns Validation result
   */
  public static validate<T>(config: T, schema: ValidationSchema): IValidationResult {
    const errors: string[] = [];
    const validatedConfig = { ...config };

    // Validate each field against the schema
    for (const [key, rules] of Object.entries(schema)) {
      const value = config[key];

      // Check if required
      if (rules.required && (value === undefined || value === null)) {
        errors.push(`${key} is required`);
        continue;
      }

      // If not present and not required, apply default if available
      if ((value === undefined || value === null)) {
        if (rules.default !== undefined) {
          validatedConfig[key] = rules.default;
        }
        continue;
      }

      // Type validation
      if (value !== undefined && value !== null) {
        const valueType = Array.isArray(value) ? 'array' : typeof value;
        if (valueType !== rules.type) {
          errors.push(`${key} must be of type ${rules.type}, got ${valueType}`);
          continue;
        }

        // Type-specific validations
        switch (rules.type) {
          case 'number':
            if (rules.min !== undefined && value < rules.min) {
              errors.push(`${key} must be at least ${rules.min}`);
            }
            if (rules.max !== undefined && value > rules.max) {
              errors.push(`${key} must be at most ${rules.max}`);
            }
            break;

          case 'string':
            if (rules.minLength !== undefined && value.length < rules.minLength) {
              errors.push(`${key} must be at least ${rules.minLength} characters`);
            }
            if (rules.maxLength !== undefined && value.length > rules.maxLength) {
              errors.push(`${key} must be at most ${rules.maxLength} characters`);
            }
            if (rules.pattern && !rules.pattern.test(value)) {
              errors.push(`${key} must match pattern ${rules.pattern}`);
            }
            break;

          case 'array':
            if (rules.minLength !== undefined && value.length < rules.minLength) {
              errors.push(`${key} must have at least ${rules.minLength} items`);
            }
            if (rules.maxLength !== undefined && value.length > rules.maxLength) {
              errors.push(`${key} must have at most ${rules.maxLength} items`);
            }
            if (rules.items && value.length > 0) {
              for (let i = 0; i < value.length; i++) {
                const itemType = Array.isArray(value[i]) ? 'array' : typeof value[i];
                if (itemType !== rules.items.type) {
                  errors.push(`${key}[${i}] must be of type ${rules.items.type}, got ${itemType}`);
                } else if (rules.items.schema && itemType === 'object') {
                  const itemResult = this.validate(value[i], rules.items.schema);
                  if (!itemResult.valid) {
                    errors.push(...itemResult.errors!.map(err => `${key}[${i}].${err}`));
                  }
                }
              }
            }
            break;

          case 'object':
            if (rules.schema) {
              const nestedResult = this.validate(value, rules.schema);
              if (!nestedResult.valid) {
                errors.push(...nestedResult.errors!.map(err => `${key}.${err}`));
              }
              validatedConfig[key] = nestedResult.config;
            }
            break;
        }

        // Enum validation
        if (rules.enum && !rules.enum.includes(value)) {
          errors.push(`${key} must be one of [${rules.enum.join(', ')}]`);
        }

        // Custom validation
        if (rules.validate) {
          const result = rules.validate(value);
          if (result !== true) {
            errors.push(typeof result === 'string' ? result : `${key} failed custom validation`);
          }
        }
      }
    }

    return {
      valid: errors.length === 0,
      errors: errors.length > 0 ? errors : undefined,
      config: validatedConfig
    };
  }


  /**
   * Apply defaults to a configuration object based on a schema
   * 
   * @param config Configuration object to apply defaults to
   * @param schema Validation schema with defaults
   * @returns Configuration with defaults applied
   */
  public static applyDefaults<T>(config: T, schema: ValidationSchema): T {
    const result = { ...config };

    for (const [key, rules] of Object.entries(schema)) {
      if (result[key] === undefined && rules.default !== undefined) {
        result[key] = rules.default;
      }

      // Apply defaults to nested objects
      if (result[key] && rules.type === 'object' && rules.schema) {
        result[key] = this.applyDefaults(result[key], rules.schema);
      }

      // Apply defaults to array items
      if (result[key] && rules.type === 'array' && rules.items && rules.items.schema) {
        result[key] = result[key].map(item =>
          typeof item === 'object' ? this.applyDefaults(item, rules.items!.schema!) : item
        );
      }
    }

    return result;
  }

  /**
   * Throw a validation error if the configuration is invalid
   * 
   * @param config Configuration to validate
   * @param schema Validation schema
   * @returns Validated configuration with defaults
   * @throws ValidationError if validation fails
   */
  public static validateOrThrow<T>(config: T, schema: ValidationSchema): T {
    const result = this.validate(config, schema);
    
    if (!result.valid) {
      throw new ValidationError(
        `Configuration validation failed: ${result.errors!.join(', ')}`,
        'CONFIG_VALIDATION_ERROR',
        { data: { errors: result.errors } }
      );
    }
    
    return result.config;
  }
}