/**
 * Configuration Management System
 * Provides progressive configuration with sensible defaults, file-based config, 
 * environment variables, and CLI argument overrides
 */

import { readFileSync, existsSync, writeFileSync } from 'fs';
import { join, resolve } from 'path';
import { homedir } from 'os';
import { WCAGLevel } from '../types/wcag';
import { ScoringProfile } from '../scoring/accessibility-scorer';
import yaml from 'js-yaml';
import Ajv from 'ajv';

// Define JSON Schema for A11yAnalyzeConfig (partial, for demonstration; expand as needed)
// NOTE: Use 'any' for now to allow extensibility; replace with full JSONSchemaType<A11yAnalyzeConfig> for strict typing
const configSchema: any = {
  type: 'object',
  properties: {
    scanning: {
      type: 'object',
      properties: {
        wcagLevel: { type: 'string', enum: ['A', 'AA', 'AAA', 'ARIA'] },
        includeAAA: { type: 'boolean' },
        includeARIA: { type: 'boolean' },
        includeWarnings: { type: 'boolean' },
        timeout: { type: 'integer', minimum: 1 },
        retries: { type: 'integer', minimum: 0 },
        retryDelay: { type: 'integer', minimum: 0 },
        waitForNetworkIdle: { type: 'boolean' },
        captureScreenshots: { type: 'boolean' },
        screenshotOnFailure: { type: 'boolean' },
        customRules: { type: 'array', items: { type: 'string' } },
        disabledRules: { type: 'array', items: { type: 'string' } },
      },
      required: [
        'wcagLevel', 'includeAAA', 'includeARIA', 'includeWarnings', 'timeout', 'retries', 'retryDelay',
        'waitForNetworkIdle', 'captureScreenshots', 'screenshotOnFailure', 'customRules', 'disabledRules'
      ],
      additionalProperties: true
    },
    browser: { type: 'object', additionalProperties: true },
    crawling: { type: 'object', additionalProperties: true },
    output: { type: 'object', additionalProperties: true },
    scoring: { type: 'object', additionalProperties: true },
    issues: { type: 'object', additionalProperties: true },
    performance: { type: 'object', additionalProperties: true },
    advanced: { type: 'object', additionalProperties: true },
    vpat: { type: 'object', additionalProperties: true },
    storybook: { type: 'object', additionalProperties: true },
    reporting: { type: 'object', additionalProperties: true },
  },
  required: [
    'scanning', 'browser', 'crawling', 'output', 'scoring', 'issues', 'performance', 'advanced', 'vpat', 'storybook', 'reporting'
  ],
  additionalProperties: false
};

const ajv = new Ajv({ allErrors: true });
const validateConfigSchema = ajv.compile(configSchema);

export interface VpatConfig {
  enabled: boolean;
  mode: 'component' | 'page' | 'storybook';
  outputFormat: 'json' | 'markdown' | 'vpat2';
  section508: boolean;
  remarks: boolean;
  jiraIntegration: boolean;
}

export interface StorybookConfig {
  enabled: boolean;
  url: string;
  iframeSelector: string;
  componentIsolation: boolean;
  autoDiscover: boolean;
}

export interface ReportingConfig {
  format: 'json' | 'markdown' | 'vpat2' | 'section508';
  templatePath?: string;
  jiraIntegration: boolean;
  includeScreenshots: boolean;
  includeSummary: boolean;
}

/**
 * Core configuration interface
 */
export interface A11yAnalyzeConfig {
  /** Scanning configuration */
  scanning: {
    wcagLevel: WCAGLevel;
    includeAAA: boolean;
    includeARIA: boolean;
    includeWarnings: boolean;
    timeout: number;
    retries: number;
    retryDelay: number;
    waitForNetworkIdle: boolean;
    captureScreenshots: boolean;
    screenshotOnFailure: boolean;
    customRules: string[];
    disabledRules: string[];
  };

  /** Browser configuration */
  browser: {
    headless: boolean;
    viewport: {
      width: number;
      height: number;
    };
    userAgent: string;
    enableJavaScript: boolean;
    allowInsecure: boolean;
    locale: string;
    timezone: string;
  };

  /** Crawling configuration */
  crawling: {
    maxDepth: number;
    maxPages: number;
    maxConcurrency: number;
    requestDelay: number;
    respectRobotsTxt: boolean;
    followRedirects: boolean;
    allowedDomains: string[];
    excludedDomains: string[];
    excludedPaths: string[];
    includedPaths: string[];
    useSitemaps: boolean;
    discoverExternalLinks: boolean;
    maxExternalDepth: number;
  };

  /** Output and reporting configuration */
  output: {
    format: 'json' | 'console' | 'both';
    verbose: boolean;
    quiet: boolean;
    debug: boolean;
    colors: boolean;
    timestamps: boolean;
    progressBars: boolean;
    outputPath?: string;
    templatePath?: string;
    exportErrors: boolean;
    errorLogPath?: string;
  };

  /** Scoring configuration */
  scoring: {
    profile: ScoringProfile;
    minScore: number;
    maxScore: number;
    enableBonuses: boolean;
    enablePenalties: boolean;
    minSeverity: 'critical' | 'serious' | 'moderate' | 'minor' | 'warning';
    customWeights?: {
      wcagLevels?: Record<WCAGLevel, number>;
      severities?: Record<string, number>;
    };
  };

  /** Issue processing configuration */
  issues: {
    groupSimilar: boolean;
    includeRemediation: boolean;
    includeCodeExamples: boolean;
    includeTestingGuidance: boolean;
    contextAware: boolean;
    maxCodeExamples: number;
    priorityMode: 'severity' | 'impact' | 'effort' | 'quickWins';
  };

  /** Performance and resilience configuration */
  performance: {
    enableCircuitBreaker: boolean;
    circuitBreakerThreshold: number;
    maxRetries: number;
    baseRetryDelay: number;
    timeoutStrategy: 'fixed' | 'adaptive';
    memoryLimit: number;
    resourceCleanup: boolean;
  };

  /** Advanced configuration */
  advanced: {
    experimentalFeatures: boolean;
    cacheResults: boolean;
    cacheTTL: number;
    telemetry: boolean;
    updateCheck: boolean;
    configVersion: string;
  };

  vpat: VpatConfig;
  storybook: StorybookConfig;
  reporting: ReportingConfig;
}

/**
 * Configuration file formats supported
 */
export type ConfigFormat = 'json' | 'js' | 'yaml';

/**
 * Configuration source information
 */
export interface ConfigSource {
  type: 'defaults' | 'file' | 'environment' | 'cli';
  path?: string;
  priority: number;
  loaded: boolean;
  errors?: string[];
}

/**
 * Configuration loading result
 */
export interface ConfigLoadResult {
  config: A11yAnalyzeConfig;
  sources: ConfigSource[];
  warnings: string[];
  errors: string[];
}

/**
 * Configuration Manager
 * Handles loading, merging, and validation of configuration from multiple sources
 */
export class ConfigManager {
  private static readonly CONFIG_FILENAMES = [
    '.a11yanalyzerc.json',
    '.a11yanalyzerc.js',
    'a11yanalyze.config.json',
    'a11yanalyze.config.js',
    'package.json', // Look for a11yanalyze key
  ];

  private static readonly DEFAULT_CONFIG: A11yAnalyzeConfig = {
    scanning: {
      wcagLevel: 'AA',
      includeAAA: false,
      includeARIA: true,
      includeWarnings: true,
      timeout: 30000,
      retries: 2,
      retryDelay: 1000,
      waitForNetworkIdle: true,
      captureScreenshots: false,
      screenshotOnFailure: true,
      customRules: [],
      disabledRules: [],
    },
    browser: {
      headless: true,
      viewport: {
        width: 1280,
        height: 720,
      },
      userAgent: 'A11yAnalyze/1.0.0 (Accessibility Testing Tool)',
      enableJavaScript: true,
      allowInsecure: false,
      locale: 'en-US',
      timezone: 'UTC',
    },
    crawling: {
      maxDepth: 2,
      maxPages: 50,
      maxConcurrency: 3,
      requestDelay: 1000,
      respectRobotsTxt: false,
      followRedirects: true,
      allowedDomains: [],
      excludedDomains: [],
      excludedPaths: [],
      includedPaths: [],
      useSitemaps: false,
      discoverExternalLinks: false,
      maxExternalDepth: 1,
    },
    output: {
      format: 'console',
      verbose: false,
      quiet: false,
      debug: false,
      colors: true,
      timestamps: false,
      progressBars: true,
      exportErrors: false,
    },
    scoring: {
      profile: 'balanced',
      minScore: 0,
      maxScore: 100,
      enableBonuses: true,
      enablePenalties: true,
      minSeverity: 'moderate',
    },
    issues: {
      groupSimilar: true,
      includeRemediation: true,
      includeCodeExamples: true,
      includeTestingGuidance: true,
      contextAware: true,
      maxCodeExamples: 3,
      priorityMode: 'impact',
    },
    performance: {
      enableCircuitBreaker: true,
      circuitBreakerThreshold: 5,
      maxRetries: 3,
      baseRetryDelay: 1000,
      timeoutStrategy: 'adaptive',
      memoryLimit: 512, // MB
      resourceCleanup: true,
    },
    advanced: {
      experimentalFeatures: false,
      cacheResults: false,
      cacheTTL: 3600, // 1 hour
      telemetry: false,
      updateCheck: true,
      configVersion: '1.0.0',
    },
    vpat: {
      enabled: false,
      mode: 'component',
      outputFormat: 'json',
      section508: true,
      remarks: true,
      jiraIntegration: false,
    },
    storybook: {
      enabled: false,
      url: '',
      iframeSelector: '#storybook-preview-iframe',
      componentIsolation: true,
      autoDiscover: true,
    },
    reporting: {
      format: 'json',
      templatePath: undefined,
      jiraIntegration: false,
      includeScreenshots: true,
      includeSummary: true,
    },
  };

  private loadedConfig?: A11yAnalyzeConfig;
  private configSources: ConfigSource[] = [];

  /**
   * Load configuration from all sources with proper priority
   */
  async loadConfig(
    searchPaths: string[] = [],
    cliOptions: Record<string, any> = {}
  ): Promise<ConfigLoadResult> {
    const warnings: string[] = [];
    const errors: string[] = [];
    this.configSources = [];

    try {
      // 1. Start with default configuration
      let config = this.deepClone(ConfigManager.DEFAULT_CONFIG);
      this.addConfigSource('defaults', 0, true);

      // 2. Load from configuration files
      const fileConfig = await this.loadFromFiles(searchPaths);
      if (fileConfig.config) {
        config = this.mergeConfigs(config, fileConfig.config);
        this.addConfigSource('file', 1, true, fileConfig.path);
      }
      warnings.push(...fileConfig.warnings);
      errors.push(...fileConfig.errors);

      // 3. Load from environment variables
      const envConfig = this.loadFromEnvironment();
      if (envConfig.config) {
        config = this.mergeConfigs(config, envConfig.config);
        this.addConfigSource('environment', 2, true);
      }
      warnings.push(...envConfig.warnings);

      // 4. Apply CLI options (highest priority)
      const cliConfig = this.convertCliToConfig(cliOptions);
      if (cliConfig.config) {
        config = this.mergeConfigs(config, cliConfig.config);
        this.addConfigSource('cli', 3, true);
      }

      // 5. Validate final configuration
      const validation = this.validateConfig(config);
      warnings.push(...validation.warnings);
      errors.push(...validation.errors);

      // Store loaded configuration
      this.loadedConfig = config;

      return {
        config,
        sources: this.configSources,
        warnings,
        errors,
      };

    } catch (error) {
      errors.push(`Configuration loading failed: ${error instanceof Error ? error.message : String(error)}`);
      
      // Return default config on failure
      return {
        config: this.deepClone(ConfigManager.DEFAULT_CONFIG),
        sources: [{ type: 'defaults', priority: 0, loaded: true }],
        warnings,
        errors,
      };
    }
  }

  /**
   * Get the currently loaded configuration
   */
  getConfig(): A11yAnalyzeConfig | null {
    return this.loadedConfig || null;
  }

  /**
   * Get configuration sources information
   */
  getConfigSources(): ConfigSource[] {
    return [...this.configSources];
  }

  /**
   * Generate a sample configuration file
   */
  generateSampleConfig(format: ConfigFormat = 'json'): string {
    const config = ConfigManager.DEFAULT_CONFIG;
    
    switch (format) {
      case 'json':
        return JSON.stringify(config, null, 2);
      
      case 'js':
        return `module.exports = ${JSON.stringify(config, null, 2)};`;
      
      case 'yaml':
        // Simple YAML generation - in production, use a proper YAML library
        return this.convertToYaml(config);
      
      default:
        throw new Error(`Unsupported config format: ${format}`);
    }
  }

  /**
   * Save configuration to file
   */
  async saveConfig(
    config: any,
    path: string,
    format: ConfigFormat = 'json'
  ): Promise<void> {
    try {
      const fullConfig = this.mergeConfigs(ConfigManager.DEFAULT_CONFIG, config);
      const content = this.generateSampleConfig(format);
      writeFileSync(path, content, 'utf8');
    } catch (error) {
      throw new Error(`Failed to save configuration: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  /**
   * Get default configuration
   */
  static getDefaultConfig(): A11yAnalyzeConfig {
    return this.prototype.deepClone(ConfigManager.DEFAULT_CONFIG);
  }

  /**
   * Load configuration from files
   * @private
   */
  private async loadFromFiles(searchPaths: string[]): Promise<{
    config?: Partial<A11yAnalyzeConfig>;
    path?: string;
    warnings: string[];
    errors: string[];
  }> {
    const warnings: string[] = [];
    const errors: string[] = [];

    // Build search paths
    const paths = [
      ...searchPaths,
      process.cwd(),
      homedir(),
    ];

    for (const basePath of paths) {
      for (const filename of ConfigManager.CONFIG_FILENAMES) {
        const configPath = resolve(basePath, filename);
        
        if (existsSync(configPath)) {
          try {
            const config = await this.loadConfigFile(configPath);
            return { config, path: configPath, warnings, errors };
          } catch (error) {
            errors.push(`Failed to load config from ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
          }
        }
      }
    }

    return { warnings, errors };
  }

  /**
   * Load a specific configuration file
   * @private
   */
  private async loadConfigFile(path: string): Promise<Partial<A11yAnalyzeConfig>> {
    const content = readFileSync(path, 'utf8');
    
    if (path.endsWith('.json')) {
      return JSON.parse(content);
    } else if (path.endsWith('.js')) {
      // For .js files, use dynamic import or require
      delete require.cache[require.resolve(path)];
      return require(path);
    } else if (path.endsWith('.yaml') || path.endsWith('.yml')) {
      return yaml.load(content) as Partial<A11yAnalyzeConfig>;
    } else if (path.includes('package.json')) {
      const pkg = JSON.parse(content);
      return pkg.a11yanalyze || {};
    }

    throw new Error(`Unsupported config file format: ${path}`);
  }

  /**
   * Load configuration from environment variables
   * @private
   */
  private loadFromEnvironment(): {
    config?: Partial<A11yAnalyzeConfig>;
    warnings: string[];
  } {
    const warnings: string[] = [];
    const config: any = {};

    // Map environment variables to config structure
    const envMappings: Record<string, string> = {
      'A11Y_WCAG_LEVEL': 'scanning.wcagLevel',
      'A11Y_INCLUDE_AAA': 'scanning.includeAAA',
      'A11Y_INCLUDE_ARIA': 'scanning.includeARIA',
      'A11Y_TIMEOUT': 'scanning.timeout',
      'A11Y_HEADLESS': 'browser.headless',
      'A11Y_VIEWPORT_WIDTH': 'browser.viewport.width',
      'A11Y_VIEWPORT_HEIGHT': 'browser.viewport.height',
      'A11Y_MAX_DEPTH': 'crawling.maxDepth',
      'A11Y_MAX_PAGES': 'crawling.maxPages',
      'A11Y_CONCURRENCY': 'crawling.maxConcurrency',
      'A11Y_OUTPUT_FORMAT': 'output.format',
      'A11Y_VERBOSE': 'output.verbose',
      'A11Y_QUIET': 'output.quiet',
      'A11Y_DEBUG': 'output.debug',
      'A11Y_SCORING_PROFILE': 'scoring.profile',
      'A11Y_MIN_SEVERITY': 'scoring.minSeverity',
    };

         for (const [envVar, configPath] of Object.entries(envMappings)) {
       const value = process.env[envVar];
       if (value !== undefined && value !== null) {
         try {
           this.setNestedValue(config, configPath, this.parseEnvValue(value));
         } catch (error) {
           warnings.push(`Invalid environment variable ${envVar}: ${error instanceof Error ? error.message : String(error)}`);
         }
       }
     }

    return { config: Object.keys(config).length > 0 ? config : undefined, warnings };
  }

  /**
   * Convert CLI options to configuration structure
   * @private
   */
  private convertCliToConfig(cliOptions: Record<string, any>): {
    config?: Partial<A11yAnalyzeConfig>;
  } {
    const config: any = {};

    // Map CLI options to config structure
    const mappings: Record<string, string> = {
      'wcagLevel': 'scanning.wcagLevel',
      'includeAaa': 'scanning.includeAAA',
      'includeAria': 'scanning.includeARIA',
      'timeout': 'scanning.timeout',
      'viewport': 'browser.viewport',
      'depth': 'crawling.maxDepth',
      'maxPages': 'crawling.maxPages',
      'concurrency': 'crawling.maxConcurrency',
      'format': 'output.format',
      'verbose': 'output.verbose',
      'quiet': 'output.quiet',
      'debug': 'output.debug',
      'output': 'output.outputPath',
      'exportErrors': 'output.exportErrors',
      'profile': 'scoring.profile',
      'minSeverity': 'scoring.minSeverity',
      'screenshot': 'scanning.captureScreenshots',
    };

    for (const [cliKey, configPath] of Object.entries(mappings)) {
      const value = cliOptions[cliKey];
      if (value !== undefined) {
        if (cliKey === 'viewport' && typeof value === 'string') {
          // Parse viewport string like "1280x720"
          const match = value.match(/^(\d+)x(\d+)$/);
          if (match && match[1] && match[2]) {
            this.setNestedValue(config, 'browser.viewport.width', parseInt(match[1], 10));
            this.setNestedValue(config, 'browser.viewport.height', parseInt(match[2], 10));
          }
        } else {
          this.setNestedValue(config, configPath, value);
        }
      }
    }

    return { config: Object.keys(config).length > 0 ? config : undefined };
  }

  /**
   * Validate configuration
   * @private
   */
  private validateConfig(config: A11yAnalyzeConfig): {
    warnings: string[];
    errors: string[];
  } {
    const warnings: string[] = [];
    const errors: string[] = [];

    // JSON Schema validation
    const valid = validateConfigSchema(config);
    if (!valid) {
      for (const err of validateConfigSchema.errors || []) {
        errors.push(`Config schema error: ${err.instancePath} ${err.message}`);
      }
    }

    // Validate WCAG level
    const validWcagLevels: WCAGLevel[] = ['A', 'AA', 'AAA', 'ARIA'];
    if (!validWcagLevels.includes(config.scanning.wcagLevel)) {
      errors.push(`Invalid WCAG level: ${config.scanning.wcagLevel}. Must be one of: ${validWcagLevels.join(', ')}`);
    }

    // Validate scoring profile
    const validProfiles: ScoringProfile[] = ['strict', 'balanced', 'lenient', 'enterprise', 'custom'];
    if (!validProfiles.includes(config.scoring.profile)) {
      errors.push(`Invalid scoring profile: ${config.scoring.profile}. Must be one of: ${validProfiles.join(', ')}`);
    }

    // Validate timeout values
    if (config.scanning.timeout <= 0) {
      errors.push('Scanning timeout must be greater than 0');
    }

    // Validate viewport
    if (config.browser.viewport.width <= 0 || config.browser.viewport.height <= 0) {
      errors.push('Browser viewport dimensions must be greater than 0');
    }

    // Validate crawling limits
    if (config.crawling.maxDepth < 0) {
      errors.push('Max crawl depth cannot be negative');
    }

    if (config.crawling.maxPages <= 0) {
      warnings.push('Max pages is 0 or negative, this may prevent scanning');
    }

    // Validate concurrency
    if (config.crawling.maxConcurrency <= 0) {
      errors.push('Max concurrency must be greater than 0');
    }

    return { warnings, errors };
  }

  /**
   * Merge two configuration objects deeply
   * @private
   */
  private mergeConfigs(base: A11yAnalyzeConfig, override: Partial<A11yAnalyzeConfig>): A11yAnalyzeConfig {
    const result = this.deepClone(base);
    return this.deepMerge(result, override);
  }

  /**
   * Deep clone an object
   * @private
   */
  private deepClone<T>(obj: T): T {
    if (obj === null || typeof obj !== 'object') return obj;
    if (obj instanceof Date) return new Date(obj.getTime()) as any;
    if (obj instanceof Array) return obj.map(item => this.deepClone(item)) as any;
    
    const cloned = {} as any;
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        cloned[key] = this.deepClone(obj[key]);
      }
    }
    return cloned;
  }

  /**
   * Deep merge objects
   * @private
   */
  private deepMerge(target: any, source: any): any {
    for (const key in source) {
      if (source.hasOwnProperty(key)) {
        if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
          if (!target[key] || typeof target[key] !== 'object') {
            target[key] = {};
          }
          this.deepMerge(target[key], source[key]);
        } else {
          target[key] = source[key];
        }
      }
    }
    return target;
  }

  /**
   * Set nested object value using dot notation
   * @private
   */
  private setNestedValue(obj: any, path: string, value: any): void {
    const keys = path.split('.');
    if (keys.length === 0) return;
    
    let current = obj;
    
    for (let i = 0; i < keys.length - 1; i++) {
      const key = keys[i];
      if (!key) continue;
      
      if (!current[key] || typeof current[key] !== 'object') {
        current[key] = {};
      }
      current = current[key];
    }
    
    const finalKey = keys[keys.length - 1];
    if (finalKey) {
      current[finalKey] = value;
    }
  }

  /**
   * Parse environment variable value to appropriate type
   * @private
   */
  private parseEnvValue(value: string): any {
    // Boolean values
    if (value.toLowerCase() === 'true') return true;
    if (value.toLowerCase() === 'false') return false;
    
    // Number values
    if (/^\d+$/.test(value)) return parseInt(value, 10);
    if (/^\d+\.\d+$/.test(value)) return parseFloat(value);
    
    // Array values (comma-separated)
    if (value.includes(',')) {
      return value.split(',').map(v => v.trim());
    }
    
    // String value
    return value;
  }

  /**
   * Convert configuration to YAML format (simplified)
   * @private
   */
  private convertToYaml(obj: any, indent = 0): string {
    const spaces = '  '.repeat(indent);
    let yaml = '';
    
    for (const [key, value] of Object.entries(obj)) {
      if (value && typeof value === 'object' && !Array.isArray(value)) {
        yaml += `${spaces}${key}:\n${this.convertToYaml(value, indent + 1)}`;
      } else if (Array.isArray(value)) {
        yaml += `${spaces}${key}:\n`;
        for (const item of value) {
          yaml += `${spaces}  - ${JSON.stringify(item)}\n`;
        }
      } else {
        yaml += `${spaces}${key}: ${JSON.stringify(value)}\n`;
      }
    }
    
    return yaml;
  }

  /**
   * Add configuration source to tracking
   * @private
   */
  private addConfigSource(
    type: ConfigSource['type'],
    priority: number,
    loaded: boolean,
    path?: string,
    errors?: string[]
  ): void {
    this.configSources.push({
      type,
      priority,
      loaded,
      path,
      errors,
    });
  }
} 