/**
 * Report Generator — produces report.md and bugs.json from crawl results
 */

import * as fs from 'fs';
import * as path from 'path';
import type { Bug } from './bug-detector.js';

interface CrawlResult {
  device: string;
  browser: string;
  bugs: Bug[];
  screenshots: string[];
  pagesVisited: number;
  actionsPerformed: number;
  durationMs: number;
  networkSummary: { total: number; errors: number; slow: number };
  consoleErrorCount: number;
}

export class ReportGenerator {
  private outputDir: string;

  constructor(outputDir: string) {
    this.outputDir = outputDir;
    fs.mkdirSync(outputDir, { recursive: true });
  }

  generate(results: CrawlResult[]): { reportPath: string; bugsPath: string } {
    // Merge and deduplicate all bugs across combos
    const allBugs = this.deduplicateBugs(results.flatMap((r) => r.bugs));

    // Sort by severity
    const sorted = allBugs.sort((a, b) => {
      const order = { P0: 0, P1: 1, P2: 2 };
      return (order[a.severity] ?? 3) - (order[b.severity] ?? 3);
    });

    const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
    const reportPath = path.join(this.outputDir, `report_${timestamp}.md`);
    const bugsPath = path.join(this.outputDir, `bugs_${timestamp}.json`);

    // Generate markdown report
    const md = this.buildMarkdown(sorted, results, timestamp);
    fs.writeFileSync(reportPath, md);

    // Generate JSON
    const json = {
      generatedAt: new Date().toISOString(),
      summary: {
        totalBugs: sorted.length,
        p0: sorted.filter((b) => b.severity === 'P0').length,
        p1: sorted.filter((b) => b.severity === 'P1').length,
        p2: sorted.filter((b) => b.severity === 'P2').length,
        combosRun: results.length,
        totalPages: new Set(results.flatMap((r) => Array.from({ length: r.pagesVisited }))).size,
        totalActions: results.reduce((s, r) => s + r.actionsPerformed, 0),
      },
      bugs: sorted,
      results: results.map((r) => ({
        device: r.device,
        browser: r.browser,
        pagesVisited: r.pagesVisited,
        actionsPerformed: r.actionsPerformed,
        durationMs: r.durationMs,
        bugCount: r.bugs.length,
        networkSummary: r.networkSummary,
        consoleErrorCount: r.consoleErrorCount,
      })),
    };
    fs.writeFileSync(bugsPath, JSON.stringify(json, null, 2));

    return { reportPath, bugsPath };
  }

  private buildMarkdown(bugs: Bug[], results: CrawlResult[], timestamp: string): string {
    const p0 = bugs.filter((b) => b.severity === 'P0');
    const p1 = bugs.filter((b) => b.severity === 'P1');
    const p2 = bugs.filter((b) => b.severity === 'P2');

    let md = '';

    // Header
    md += `# Cockroach Bug Report\n\n`;
    md += `**Generated**: ${new Date().toISOString()}\n`;
    md += `**Target**: Rainmaker (logged-out, mobile-first)\n\n`;

    // Summary
    md += `## Summary\n\n`;
    md += `| Metric | Value |\n`;
    md += `|--------|-------|\n`;
    md += `| Total Bugs | **${bugs.length}** |\n`;
    md += `| P0 (Critical) | **${p0.length}** |\n`;
    md += `| P1 (Major) | **${p1.length}** |\n`;
    md += `| P2 (Minor) | **${p2.length}** |\n`;
    md += `| Device/Browser Combos | ${results.length} |\n`;
    md += `| Total Actions | ${results.reduce((s, r) => s + r.actionsPerformed, 0)} |\n`;
    md += `| Total Duration | ${Math.round(results.reduce((s, r) => s + r.durationMs, 0) / 1000)}s |\n\n`;

    // Device/Browser matrix
    md += `## Test Matrix\n\n`;
    md += `| Browser | Device | Pages | Actions | Bugs | Duration |\n`;
    md += `|---------|--------|-------|---------|------|----------|\n`;
    for (const r of results) {
      md += `| ${r.browser} | ${r.device} | ${r.pagesVisited} | ${r.actionsPerformed} | ${r.bugs.length} | ${Math.round(r.durationMs / 1000)}s |\n`;
    }
    md += '\n';

    // P0 Bugs
    if (p0.length > 0) {
      md += `## P0 — Critical Bugs\n\n`;
      for (const bug of p0) {
        md += this.bugBlock(bug);
      }
    }

    // P1 Bugs
    if (p1.length > 0) {
      md += `## P1 — Major Bugs\n\n`;
      for (const bug of p1) {
        md += this.bugBlock(bug);
      }
    }

    // P2 Bugs
    if (p2.length > 0) {
      md += `## P2 — Minor Bugs\n\n`;
      for (const bug of p2) {
        md += this.bugBlock(bug);
      }
    }

    if (bugs.length === 0) {
      md += `## No Bugs Found\n\nAll tests passed without issues.\n\n`;
    }

    // Network summary
    md += `## Network Summary\n\n`;
    for (const r of results) {
      md += `- **${r.browser}/${r.device}**: ${r.networkSummary.total} requests, ${r.networkSummary.errors} errors, ${r.networkSummary.slow} slow\n`;
    }
    md += '\n';

    return md;
  }

  private bugBlock(bug: Bug): string {
    let md = `### ${bug.id}: ${bug.title}\n\n`;
    md += `- **Severity**: ${bug.severity}\n`;
    md += `- **Category**: ${bug.category}\n`;
    md += `- **Device**: ${bug.device}\n`;
    md += `- **Browser**: ${bug.browser}\n`;
    md += `- **URL**: ${bug.url}\n`;
    md += `- **Owner**: ${bug.suggestedOwner}\n`;
    md += `- **Timestamp**: ${bug.timestamp}\n\n`;

    md += `**Expected**: ${bug.expected}\n\n`;
    md += `**Actual**: ${bug.actual}\n\n`;

    if (bug.stepsToReproduce.length > 0) {
      md += `**Steps to Reproduce**:\n`;
      for (const step of bug.stepsToReproduce.slice(-5)) {
        md += `1. ${step}\n`;
      }
      md += '\n';
    }

    if (bug.consoleExcerpt) {
      md += `**Console**:\n\`\`\`\n${bug.consoleExcerpt}\n\`\`\`\n\n`;
    }

    if (bug.networkExcerpt) {
      md += `**Network**:\n\`\`\`\n${bug.networkExcerpt}\n\`\`\`\n\n`;
    }

    if (bug.screenshot) {
      md += `**Screenshot**: ${bug.screenshot}\n\n`;
    }

    md += `---\n\n`;
    return md;
  }

  private deduplicateBugs(bugs: Bug[]): Bug[] {
    const seen = new Map<string, Bug>();
    for (const bug of bugs) {
      // Key by category + title + URL for cross-device dedup
      const key = `${bug.category}|${bug.title}|${bug.url}`;
      if (!seen.has(key)) {
        seen.set(key, bug);
      } else {
        // Append device info to existing
        const existing = seen.get(key)!;
        if (!existing.device.includes(bug.device)) {
          existing.device += `, ${bug.device}`;
        }
        if (!existing.browser.includes(bug.browser)) {
          existing.browser += `, ${bug.browser}`;
        }
      }
    }
    return Array.from(seen.values());
  }
}
