import { execSync } from 'child_process';
import { existsSync, readFileSync } from 'fs';
import { join } from 'path';

export interface VulnerabilityReport {
  package: string;
  version: string;
  vulnerability: {
    id: string;
    title: string;
    severity: 'low' | 'medium' | 'high' | 'critical';
    description: string;
    references: string[];
    cwe?: string[];
    cvss?: {
      score: number;
      vector: string;
    };
  };
  fixAvailable: {
    available: boolean;
    version?: string;
    path?: string;
  };
  paths: string[];
}

export interface DependencyAuditResult {
  vulnerabilities: VulnerabilityReport[];
  summary: {
    total: number;
    critical: number;
    high: number;
    medium: number;
    low: number;
  };
  metadata: {
    totalDependencies: number;
    auditedAt: Date;
    tool: string;
    projectPath: string;
  };
}

export interface ScanOptions {
  projectPath: string;
  includeDevDependencies?: boolean;
  skipAuditFix?: boolean;
  auditLevel?: 'low' | 'moderate' | 'high' | 'critical';
  timeout?: number;
}

export class VulnerabilityScanner {
  private options: ScanOptions;

  constructor(options: ScanOptions) {
    this.options = {
      includeDevDependencies: true,
      skipAuditFix: true,
      auditLevel: 'low',
      timeout: 30000,
      ...options,
    };
  }

  async scanDependencies(): Promise<DependencyAuditResult> {
    const packageJsonPath = join(this.options.projectPath, 'package.json');

    if (!existsSync(packageJsonPath)) {
      throw new Error('package.json not found in project path');
    }

    const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
    const totalDependencies = this.countDependencies(packageJson);

    let vulnerabilities: VulnerabilityReport[] = [];

    // Try npm audit first
    try {
      vulnerabilities = await this.runNpmAudit();
    } catch (error) {
      console.warn('npm audit failed, falling back to manual checks:', error);
      vulnerabilities = await this.runManualAudit(packageJson);
    }

    const summary = this.calculateSummary(vulnerabilities);

    return {
      vulnerabilities,
      summary,
      metadata: {
        totalDependencies,
        auditedAt: new Date(),
        tool: 'npm-audit',
        projectPath: this.options.projectPath,
      },
    };
  }

  private async runNpmAudit(): Promise<VulnerabilityReport[]> {
    try {
      const command = `npm audit --json ${this.options.auditLevel !== 'low' ? `--audit-level=${this.options.auditLevel}` : ''}`;

      const result = execSync(command, {
        cwd: this.options.projectPath,
        encoding: 'utf-8',
        timeout: this.options.timeout,
        stdio: 'pipe',
      });

      const auditData = JSON.parse(result);
      return this.parseNpmAuditResult(auditData);
    } catch (error: any) {
      // npm audit returns non-zero exit code when vulnerabilities are found
      if (error.stdout) {
        try {
          const auditData = JSON.parse(error.stdout);
          return this.parseNpmAuditResult(auditData);
        } catch (parseError) {
          throw new Error(`Failed to parse npm audit output: ${parseError}`);
        }
      }
      throw error;
    }
  }

  private parseNpmAuditResult(auditData: any): VulnerabilityReport[] {
    const vulnerabilities: VulnerabilityReport[] = [];

    if (auditData.vulnerabilities) {
      Object.entries(auditData.vulnerabilities).forEach(([packageName, vulnData]: [string, any]) => {
        if (vulnData.via && Array.isArray(vulnData.via)) {
          vulnData.via.forEach((via: any) => {
            if (typeof via === 'object' && via.source) {
              vulnerabilities.push({
                package: packageName,
                version: vulnData.range || 'unknown',
                vulnerability: {
                  id: via.source.toString(),
                  title: via.title || 'Unknown vulnerability',
                  severity: this.mapSeverity(vulnData.severity),
                  description: via.title || 'No description available',
                  references: via.url ? [via.url] : [],
                  ...(via.cwe && { cwe: [via.cwe.toString()] }),
                  ...(via.cvss && {
                    cvss: {
                      score: via.cvss.score || 0,
                      vector: via.cvss.vectorString || '',
                    }
                  }),
                },
                fixAvailable: {
                  available: !!vulnData.fixAvailable,
                  version: vulnData.fixAvailable?.version,
                  path: vulnData.fixAvailable?.path,
                },
                paths: vulnData.effects || [],
              });
            }
          });
        }
      });
    }

    return vulnerabilities;
  }

  private async runManualAudit(packageJson: any): Promise<VulnerabilityReport[]> {
    const vulnerabilities: VulnerabilityReport[] = [];
    const dependencies = {
      ...(packageJson.dependencies || {}),
      ...(this.options.includeDevDependencies ? packageJson.devDependencies || {} : {}),
    };

    // Known vulnerable packages database (simplified)
    const knownVulnerabilities = await this.getKnownVulnerabilities();

    Object.entries(dependencies).forEach(([pkg, version]: [string, any]) => {
      const vulns = knownVulnerabilities.filter(v => v.package === pkg);
      vulns.forEach(vuln => {
        if (this.isVersionVulnerable(version, vuln.affectedVersions)) {
          vulnerabilities.push({
            package: pkg,
            version: version,
            vulnerability: vuln.vulnerability,
            fixAvailable: vuln.fixAvailable,
            paths: [pkg],
          });
        }
      });
    });

    return vulnerabilities;
  }

  private async getKnownVulnerabilities(): Promise<Array<{
    package: string;
    affectedVersions: string;
    vulnerability: VulnerabilityReport['vulnerability'];
    fixAvailable: VulnerabilityReport['fixAvailable'];
  }>> {
    // This would typically fetch from a vulnerability database
    // For now, return a static list of known vulnerabilities
    return [
      {
        package: 'lodash',
        affectedVersions: '<4.17.21',
        vulnerability: {
          id: 'CVE-2021-23337',
          title: 'Prototype Pollution in lodash',
          severity: 'high',
          description: 'lodash versions prior to 4.17.21 are vulnerable to Command Injection via template.',
          references: ['https://nvd.nist.gov/vuln/detail/CVE-2021-23337'],
          cwe: ['CWE-94'],
          cvss: {
            score: 7.2,
            vector: 'CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H',
          },
        },
        fixAvailable: {
          available: true,
          version: '4.17.21',
        },
      },
      {
        package: 'axios',
        affectedVersions: '<0.21.2',
        vulnerability: {
          id: 'CVE-2021-3749',
          title: 'SSRF in axios',
          severity: 'medium',
          description: 'axios is vulnerable to Server-Side Request Forgery (SSRF)',
          references: ['https://nvd.nist.gov/vuln/detail/CVE-2021-3749'],
          cwe: ['CWE-918'],
          cvss: {
            score: 5.3,
            vector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N',
          },
        },
        fixAvailable: {
          available: true,
          version: '0.21.2',
        },
      },
      {
        package: 'express',
        affectedVersions: '<4.17.3',
        vulnerability: {
          id: 'CVE-2022-24999',
          title: 'Open Redirect in express',
          severity: 'medium',
          description: 'Express.js is vulnerable to Open Redirect attacks',
          references: ['https://nvd.nist.gov/vuln/detail/CVE-2022-24999'],
          cwe: ['CWE-601'],
          cvss: {
            score: 6.1,
            vector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N',
          },
        },
        fixAvailable: {
          available: true,
          version: '4.17.3',
        },
      },
      {
        package: 'jsonwebtoken',
        affectedVersions: '<8.5.1',
        vulnerability: {
          id: 'CVE-2022-23529',
          title: 'Algorithm confusion in jsonwebtoken',
          severity: 'high',
          description: 'jsonwebtoken is vulnerable to algorithm confusion attacks',
          references: ['https://nvd.nist.gov/vuln/detail/CVE-2022-23529'],
          cwe: ['CWE-327'],
          cvss: {
            score: 7.6,
            vector: 'CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:L',
          },
        },
        fixAvailable: {
          available: true,
          version: '8.5.1',
        },
      },
    ];
  }

  private isVersionVulnerable(installedVersion: string, vulnerableRange: string): boolean {
    // Simplified version comparison - in production, use semver library
    const cleanVersion = installedVersion.replace(/[^0-9.]/g, '');
    const cleanRange = vulnerableRange.replace('<', '');

    try {
      const installed = cleanVersion.split('.').map(Number);
      const vulnerable = cleanRange.split('.').map(Number);

      for (let i = 0; i < Math.max(installed.length, vulnerable.length); i++) {
        const installedPart = installed[i] || 0;
        const vulnerablePart = vulnerable[i] || 0;

        if (installedPart < vulnerablePart) return true;
        if (installedPart > vulnerablePart) return false;
      }

      return false; // Equal versions
    } catch (error) {
      console.warn(`Failed to compare versions: ${installedVersion} vs ${vulnerableRange}`);
      return false;
    }
  }

  private countDependencies(packageJson: any): number {
    const deps = Object.keys(packageJson.dependencies || {}).length;
    const devDeps = this.options.includeDevDependencies
      ? Object.keys(packageJson.devDependencies || {}).length
      : 0;
    return deps + devDeps;
  }

  private calculateSummary(vulnerabilities: VulnerabilityReport[]): DependencyAuditResult['summary'] {
    return vulnerabilities.reduce(
      (acc, vuln) => {
        acc.total++;
        acc[vuln.vulnerability.severity]++;
        return acc;
      },
      { total: 0, critical: 0, high: 0, medium: 0, low: 0 }
    );
  }

  private mapSeverity(severity: string): 'low' | 'medium' | 'high' | 'critical' {
    switch (severity?.toLowerCase()) {
      case 'critical': return 'critical';
      case 'high': return 'high';
      case 'moderate': return 'medium';
      case 'low': return 'low';
      default: return 'medium';
    }
  }

  async generateFixScript(): Promise<string> {
    const auditResult = await this.scanDependencies();
    const fixes: string[] = [];

    auditResult.vulnerabilities.forEach(vuln => {
      if (vuln.fixAvailable.available && vuln.fixAvailable.version) {
        fixes.push(`npm install ${vuln.package}@${vuln.fixAvailable.version}`);
      }
    });

    if (fixes.length === 0) {
      return '# No automatic fixes available';
    }

    return [
      '#!/bin/bash',
      '# Automated vulnerability fixes',
      '# Generated by OrdoJS Security Scanner',
      '',
      ...fixes,
      '',
      '# Run audit again to verify fixes',
      'npm audit',
    ].join('\n');
  }
}
