/**
 * Continuous Validator
 *
 * Background watcher that validates project in real-time.
 * Detects issues before they become problems.
 *
 * MORPH-SPEC 3.0 - Sprint 4
 */

import { readFileSync, existsSync, watch } from 'fs';
import { glob } from 'glob';
import { join, dirname } from 'path';
import chalk from 'chalk';

export class ContinuousValidator {
  constructor(projectPath = '.') {
    this.projectPath = projectPath;
    this.watchers = [];
    this.validationInterval = null;
  }

  /**
   * Start continuous validation
   */
  async start(options = {}) {
    const { watchMode = true, interval = 30000 } = options;

    console.log(chalk.cyan('🔍 Starting continuous validation...'));

    // Initial validation
    await this.runAllValidations();

    if (watchMode) {
      // Watch for file changes
      this.setupFileWatchers();

      // Periodic validation
      this.validationInterval = setInterval(() => {
        this.runAllValidations();
      }, interval);

      console.log(chalk.gray(`Watching for changes (interval: ${interval/1000}s)...`));
    }
  }

  /**
   * Stop continuous validation
   */
  stop() {
    // Stop file watchers
    this.watchers.forEach(watcher => watcher.close());
    this.watchers = [];

    // Stop periodic validation
    if (this.validationInterval) {
      clearInterval(this.validationInterval);
      this.validationInterval = null;
    }

    console.log(chalk.gray('Stopped continuous validation'));
  }

  /**
   * Setup file watchers
   */
  setupFileWatchers() {
    const watchPaths = [
      '**/*.csproj',
      '**/Program.cs',
      'wwwroot/css/**/*.css',
      'infra/**/*.bicep'
    ];

    for (const pattern of watchPaths) {
      const watcher = watch(pattern, { persistent: false }, async (eventType, filename) => {
        if (filename) {
          console.log(chalk.gray(`File changed: ${filename}`));
          await this.runAllValidations();
        }
      });

      this.watchers.push(watcher);
    }
  }

  /**
   * Run all validations
   */
  async runAllValidations() {
    const results = await Promise.all([
      this.validatePackageCompatibility(),
      this.validateArchitecturePatterns(),
      this.validateProgramCs(),
      this.validateUIContrast(),
      this.validateCosts()
    ]);

    const errors = results.filter(r => r.level === 'error');
    const warnings = results.filter(r => r.level === 'warning');
    const infos = results.filter(r => r.level === 'info');

    // Display summary
    if (errors.length === 0 && warnings.length === 0) {
      console.log(chalk.green('✅ All validations passed'));
    } else {
      if (errors.length > 0) {
        console.log(chalk.red(`\n❌ ${errors.length} error(s) found:`));
        errors.forEach(e => console.log(chalk.red(`  - ${e.message}`)));
      }

      if (warnings.length > 0) {
        console.log(chalk.yellow(`\n⚠️  ${warnings.length} warning(s):`));
        warnings.forEach(w => console.log(chalk.yellow(`  - ${w.message}`)));
      }
    }

    // Auto-fix errors if possible
    const fixableErrors = errors.filter(e => e.autoFix);
    if (fixableErrors.length > 0) {
      console.log(chalk.cyan(`\n🔧 Auto-fixing ${fixableErrors.length} issue(s)...`));
      for (const error of fixableErrors) {
        try {
          await error.autoFix();
          console.log(chalk.green(`  ✅ Fixed: ${error.message}`));
        } catch (ex) {
          console.log(chalk.red(`  ❌ Failed to fix: ${error.message}`));
        }
      }
    }

    return { errors, warnings, infos };
  }

  /**
   * Validate package compatibility (.NET 10)
   */
  async validatePackageCompatibility() {
    try {
      const csprojFiles = await glob('**/*.csproj', { ignore: '**/obj/**' });

      for (const file of csprojFiles) {
        const content = readFileSync(file, 'utf-8');

        // Extract .NET version
        const tfmMatch = content.match(/<TargetFramework>(.*?)<\/TargetFramework>/);
        if (!tfmMatch) continue;

        const targetFramework = tfmMatch[1];
        const dotnetVersion = parseInt(targetFramework.replace('net', ''));

        if (dotnetVersion < 10) {
          return {
            level: 'info',
            message: `${file}: Using .NET ${dotnetVersion} (consider upgrading to .NET 10)`
          };
        }

        // Package compatibility matrix
        const incompatiblePackages = this.checkPackageCompatibility(content, dotnetVersion);

        if (incompatiblePackages.length > 0) {
          return {
            level: 'error',
            message: `${file}: Incompatible packages with .NET ${dotnetVersion}:\n` +
                     incompatiblePackages.map(p => `    - ${p.name} ${p.version} → upgrade to ${p.requiredVersion}`).join('\n'),
            packages: incompatiblePackages
          };
        }
      }

      return { level: 'ok' };
    } catch (error) {
      return { level: 'error', message: `Package validation failed: ${error.message}` };
    }
  }

  /**
   * Check package compatibility
   */
  checkPackageCompatibility(csprojContent, dotnetVersion) {
    const incompatible = [];

    // Compatibility matrix (from dotnet10-compatibility.md)
    const matrix = {
      'MudBlazor': {
        10: '8.15.0',
        pattern: /<PackageReference Include="MudBlazor" Version="(.*?)"/
      },
      'Microsoft.FluentUI.AspNetCore.Components': {
        10: '5.0.0',
        pattern: /<PackageReference Include="Microsoft\.FluentUI\.AspNetCore\.Components" Version="(.*?)"/
      },
      'Hangfire.AspNetCore': {
        10: '1.8.22',
        pattern: /<PackageReference Include="Hangfire\.AspNetCore" Version="(.*?)"/
      }
    };

    for (const [pkg, config] of Object.entries(matrix)) {
      const match = csprojContent.match(config.pattern);
      if (match) {
        const installedVersion = match[1];
        const requiredVersion = config[dotnetVersion];

        if (requiredVersion && this.compareVersions(installedVersion, requiredVersion) < 0) {
          incompatible.push({
            name: pkg,
            version: installedVersion,
            requiredVersion
          });
        }
      }
    }

    return incompatible;
  }

  /**
   * Compare semantic versions
   */
  compareVersions(v1, v2) {
    const parts1 = v1.split('.').map(Number);
    const parts2 = v2.split('.').map(Number);

    for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
      const p1 = parts1[i] || 0;
      const p2 = parts2[i] || 0;
      if (p1 < p2) return -1;
      if (p1 > p2) return 1;
    }

    return 0;
  }

  /**
   * Validate architecture patterns
   */
  async validateArchitecturePatterns() {
    try {
      const blazorFiles = await glob('**/*.razor.cs', { ignore: '**/obj/**' });

      for (const file of blazorFiles) {
        const content = readFileSync(file, 'utf-8');

        // Check for Application layer injection (anti-pattern)
        if (/\[Inject\].*I\w+Command/.test(content)) {
          return {
            level: 'error',
            message: `${file}: Blazor component injecting Application layer directly (use HttpClient pattern)`,
            reference: 'framework/standards/blazor-pitfalls.md'
          };
        }

        // Check for DbContext injection (anti-pattern)
        if (/\[Inject\].*DbContext/.test(content)) {
          return {
            level: 'error',
            message: `${file}: Blazor component injecting DbContext directly (use HttpClient → API → Handler)`,
            reference: 'framework/standards/blazor-pitfalls.md'
          };
        }

        // Check for JSRuntime in OnInitialized (pitfall)
        if (/OnInitialized.*JSRuntime/.test(content.replace(/\s+/g, ''))) {
          return {
            level: 'warning',
            message: `${file}: Possible JSRuntime call in OnInitialized (use OnAfterRenderAsync)`,
            reference: 'framework/standards/blazor-lifecycle.md'
          };
        }
      }

      return { level: 'ok' };
    } catch (error) {
      return { level: 'error', message: `Architecture validation failed: ${error.message}` };
    }
  }

  /**
   * Validate Program.cs setup
   */
  async validateProgramCs() {
    try {
      const programCs = 'Program.cs';
      if (!existsSync(programCs)) {
        return { level: 'ok' }; // Not a Blazor project
      }

      const content = readFileSync(programCs, 'utf-8');

      const checks = [
        { pattern: /app\.UseStaticFiles\(\)/, message: 'app.UseStaticFiles() is missing', critical: true },
        { pattern: /AddHttpClient\(\)/, message: 'AddHttpClient() is missing', critical: true },
        { pattern: /AddHttpContextAccessor\(\)/, message: 'AddHttpContextAccessor() is missing', critical: true }
      ];

      for (const check of checks) {
        if (!check.pattern.test(content)) {
          return {
            level: check.critical ? 'error' : 'warning',
            message: `Program.cs: ${check.message}`,
            reference: 'framework/standards/program-cs-checklist.md'
          };
        }
      }

      // Check middleware order (UseStaticFiles before UseAntiforgery)
      const staticFilesIndex = content.indexOf('UseStaticFiles');
      const antiforgeryIndex = content.indexOf('UseAntiforgery');

      if (staticFilesIndex > 0 && antiforgeryIndex > 0 && staticFilesIndex > antiforgeryIndex) {
        return {
          level: 'error',
          message: 'Program.cs: app.UseStaticFiles() must come BEFORE app.UseAntiforgery()',
          reference: 'framework/standards/program-cs-checklist.md'
        };
      }

      return { level: 'ok' };
    } catch (error) {
      return { level: 'error', message: `Program.cs validation failed: ${error.message}` };
    }
  }

  /**
   * Validate UI contrast (WCAG 2.1 AA)
   */
  async validateUIContrast() {
    try {
      const cssFiles = await glob('wwwroot/css/**/*.css', { ignore: '**/node_modules/**' });

      for (const file of cssFiles) {
        const content = readFileSync(file, 'utf-8');

        // Extract color variables
        const colors = this.extractColors(content);

        // Check common color pairs
        const issues = [];
        const bgColors = colors.filter(c => c.name.includes('background') || c.name.includes('bg'));
        const textColors = colors.filter(c => c.name.includes('text') || c.name.includes('color'));

        for (const bg of bgColors) {
          for (const text of textColors) {
            const ratio = this.calculateContrastRatio(bg.value, text.value);
            if (ratio < 4.5) { // WCAG AA minimum for text
              issues.push({ bg: bg.name, text: text.name, ratio: ratio.toFixed(2) });
            }
          }
        }

        if (issues.length > 0) {
          return {
            level: 'warning',
            message: `${file}: Low contrast detected (WCAG AA requires 4.5:1):\n` +
                     issues.map(i => `    - ${i.text} on ${i.bg}: ${i.ratio}:1`).join('\n')
          };
        }
      }

      return { level: 'ok' };
    } catch (error) {
      return { level: 'ok' }; // CSS parsing is optional
    }
  }

  /**
   * Extract colors from CSS
   */
  extractColors(css) {
    const colors = [];
    const hexPattern = /--([\w-]+):\s*(#[0-9a-fA-F]{3,6})/g;
    let match;

    while ((match = hexPattern.exec(css)) !== null) {
      colors.push({ name: match[1], value: match[2] });
    }

    return colors;
  }

  /**
   * Calculate contrast ratio (WCAG formula)
   */
  calculateContrastRatio(color1, color2) {
    const l1 = this.getLuminance(color1);
    const l2 = this.getLuminance(color2);

    const lighter = Math.max(l1, l2);
    const darker = Math.min(l1, l2);

    return (lighter + 0.05) / (darker + 0.05);
  }

  /**
   * Get relative luminance
   */
  getLuminance(hex) {
    const rgb = this.hexToRgb(hex);
    if (!rgb) return 0;

    const [r, g, b] = rgb.map(val => {
      val = val / 255;
      return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4);
    });

    return 0.2126 * r + 0.7152 * g + 0.0722 * b;
  }

  /**
   * Convert hex to RGB
   */
  hexToRgb(hex) {
    const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
    return result ? [
      parseInt(result[1], 16),
      parseInt(result[2], 16),
      parseInt(result[3], 16)
    ] : null;
  }

  /**
   * Validate costs (Bicep files)
   */
  async validateCosts() {
    try {
      const bicepFiles = await glob('infra/**/*.bicep');

      if (bicepFiles.length === 0) {
        return { level: 'ok' }; // No infrastructure
      }

      // This would integrate with cost-calculator.js
      // For now, just check if files exist
      return { level: 'info', message: `Found ${bicepFiles.length} Bicep file(s) - run 'npx morph-spec cost' to estimate costs` };
    } catch (error) {
      return { level: 'ok' };
    }
  }
}
