import type { 
  CoverageConfig, 
  CoverageReport, 
  PackageCoverage, 
  CoverageMetric,
  FileCoverage 
} from './types';

export class CoverageProcessor {
  constructor(private config: CoverageConfig) {}

  process(rawCoverage: any, testResults: any[]): CoverageReport {
    // Use the package information from raw coverage if available
    const packages = rawCoverage.packages 
      ? this.processPackagesCoverage(rawCoverage.packages)
      : this.groupByPackage(rawCoverage.files, testResults);
    
    const overall = this.calculateOverallCoverage(packages);

    return {
      timestamp: new Date().toISOString(),
      packages,
      overall,
      config: this.config,
    };
  }

  private processPackagesCoverage(packagesData: any): PackageCoverage[] {
    const packages: PackageCoverage[] = [];

    for (const [packageName, packageData] of Object.entries(packagesData)) {
      if (!packageData || typeof packageData !== 'object') continue;
      
      const pkg: PackageCoverage = {
        name: packageName,
        path: '', // Will be set from files if available
        lines: this.createMetricFromRaw(packageData.lines),
        functions: this.createMetricFromRaw(packageData.functions),
        branches: this.createMetricFromRaw(packageData.branches),
        statements: this.createMetricFromRaw(packageData.lines), // Often same as lines
        files: [],
      };

      // Process files if available
      if (packageData.files && Array.isArray(packageData.files)) {
        for (const file of packageData.files) {
          const fileCoverage = this.processFile(file);
          pkg.files.push(fileCoverage);
          
          // Set package path from first file if not set
          if (!pkg.path && file.path) {
            pkg.path = this.getPackagePath(file.path);
          }
        }
      }

      // Only include packages that have files with coverage data
      if (pkg.files.length > 0) {
        packages.push(pkg);
      }
    }

    return packages;
  }

  private createMetricFromRaw(rawMetric: any): CoverageMetric {
    if (!rawMetric || typeof rawMetric !== 'object') {
      return this.createEmptyMetric();
    }

    const total = rawMetric.found || 0;
    const covered = rawMetric.hit || 0;
    
    return {
      total,
      covered,
      skipped: 0,
      percentage: total > 0 ? (covered / total) * 100 : 100,
    };
  }

  private groupByPackage(files: any[], testResults: any[]): PackageCoverage[] {
    const packageMap = new Map<string, PackageCoverage>();

    // Group files by package
    for (const file of files) {
      const packageName = this.getPackageFromPath(file.path);
      
      if (!packageMap.has(packageName)) {
        packageMap.set(packageName, {
          name: packageName,
          path: this.getPackagePath(file.path),
          lines: this.createEmptyMetric(),
          functions: this.createEmptyMetric(),
          branches: this.createEmptyMetric(),
          statements: this.createEmptyMetric(),
          files: [],
        });
      }

      const pkg = packageMap.get(packageName)!;
      const fileCoverage = this.processFile(file);
      
      pkg.files.push(fileCoverage);
      this.addMetrics(pkg.lines, fileCoverage.lines);
      this.addMetrics(pkg.functions, fileCoverage.functions);
      this.addMetrics(pkg.branches, fileCoverage.branches);
      this.addMetrics(pkg.statements, fileCoverage.statements);
    }

    // Calculate percentages for each package
    const packages = Array.from(packageMap.values());
    for (const pkg of packages) {
      this.calculatePercentage(pkg.lines);
      this.calculatePercentage(pkg.functions);
      this.calculatePercentage(pkg.branches);
      this.calculatePercentage(pkg.statements);
    }

    return packages;
  }

  private processFile(file: any): FileCoverage {
    const lines = this.createMetric(file.lines.found, file.lines.hit);
    const functions = this.createMetric(file.functions.found, file.functions.hit);
    const branches = this.createMetric(file.branches.found, file.branches.hit);
    // Statements often equal lines in simple coverage tools
    const statements = this.createMetric(file.lines.found, file.lines.hit);

    return {
      path: file.path,
      lines,
      functions,
      branches,
      statements,
    };
  }

  private createEmptyMetric(): CoverageMetric {
    return {
      total: 0,
      covered: 0,
      skipped: 0,
      percentage: 0,
    };
  }

  private createMetric(total: number, covered: number): CoverageMetric {
    return {
      total,
      covered,
      skipped: 0,
      percentage: total > 0 ? (covered / total) * 100 : 100,
    };
  }

  private addMetrics(target: CoverageMetric, source: CoverageMetric): void {
    target.total += source.total;
    target.covered += source.covered;
    target.skipped += source.skipped;
  }

  private calculatePercentage(metric: CoverageMetric): void {
    metric.percentage = metric.total > 0 ? (metric.covered / metric.total) * 100 : 100;
  }

  private calculateOverallCoverage(packages: PackageCoverage[]): CoverageReport['overall'] {
    const overall = {
      lines: this.createEmptyMetric(),
      functions: this.createEmptyMetric(),
      branches: this.createEmptyMetric(),
      statements: this.createEmptyMetric(),
    };

    for (const pkg of packages) {
      this.addMetrics(overall.lines, pkg.lines);
      this.addMetrics(overall.functions, pkg.functions);
      this.addMetrics(overall.branches, pkg.branches);
      this.addMetrics(overall.statements, pkg.statements);
    }

    this.calculatePercentage(overall.lines);
    this.calculatePercentage(overall.functions);
    this.calculatePercentage(overall.branches);
    this.calculatePercentage(overall.statements);

    return overall;
  }

  private getPackageFromPath(filePath: string): string {
    const normalizedPath = filePath.replace(/\\/g, '/');
    
    // Try to extract package name from path
    const patterns = [
      /packages\/([^/]+)\//,
      /apps\/stock\/([^/]+)\//,
      /apps\/([^/]+)\//,
      /libs\/core\/([^/]+)\//,
      /libs\/data\/([^/]+)\//,
      /libs\/services\/([^/]+)\//,
      /libs\/([^/]+)\//,
      /tools\/([^/]+)\//,
      /@stock-bot\/([^/]+)\//,
    ];

    for (const pattern of patterns) {
      const match = normalizedPath.match(pattern);
      if (match) {
        return `@stock-bot/${match[1]}`;
      }
    }

    // Default to root
    return 'root';
  }

  private getPackagePath(filePath: string): string {
    const normalizedPath = filePath.replace(/\\/g, '/');
    
    // Extract package root path
    const patterns = [
      /(.*\/packages\/[^/]+)\//,
      /(.*\/apps\/stock\/[^/]+)\//,
      /(.*\/apps\/[^/]+)\//,
      /(.*\/libs\/core\/[^/]+)\//,
      /(.*\/libs\/data\/[^/]+)\//,
      /(.*\/libs\/services\/[^/]+)\//,
      /(.*\/libs\/[^/]+)\//,
      /(.*\/tools\/[^/]+)\//,
    ];

    for (const pattern of patterns) {
      const match = normalizedPath.match(pattern);
      if (match) {
        return match[1];
      }
    }

    // Default to workspace root
    return this.config.workspaceRoot || process.cwd();
  }

  checkThresholds(report: CoverageReport): {
    passed: boolean;
    failures: Array<{ metric: string; expected: number; actual: number }>;
  } {
    const failures: Array<{ metric: string; expected: number; actual: number }> = [];
    const { thresholds } = this.config;
    const { overall } = report;

    if (thresholds.lines !== undefined && overall.lines.percentage < thresholds.lines) {
      failures.push({
        metric: 'lines',
        expected: thresholds.lines,
        actual: overall.lines.percentage,
      });
    }

    if (thresholds.functions !== undefined && overall.functions.percentage < thresholds.functions) {
      failures.push({
        metric: 'functions',
        expected: thresholds.functions,
        actual: overall.functions.percentage,
      });
    }

    if (thresholds.branches !== undefined && overall.branches.percentage < thresholds.branches) {
      failures.push({
        metric: 'branches',
        expected: thresholds.branches,
        actual: overall.branches.percentage,
      });
    }

    if (thresholds.statements !== undefined && overall.statements.percentage < thresholds.statements) {
      failures.push({
        metric: 'statements',
        expected: thresholds.statements,
        actual: overall.statements.percentage,
      });
    }

    return {
      passed: failures.length === 0,
      failures,
    };
  }
}