#!/usr/bin/env node

import fs from "node:fs";
import path from "node:path";

const DEFAULT_INPUT = "compat-tests/reports/dual-server-latency-report.json";
const DEFAULT_OUTPUT = "compat-tests/reports/dual-server-latency-report.html";

function parseArgs(argv) {
  const args = {
    input: DEFAULT_INPUT,
    output: DEFAULT_OUTPUT,
  };

  for (let i = 2; i < argv.length; i += 1) {
    const token = argv[i];
    if ((token === "--input" || token === "-i") && argv[i + 1]) {
      args.input = argv[++i];
      continue;
    }
    if ((token === "--output" || token === "-o") && argv[i + 1]) {
      args.output = argv[++i];
      continue;
    }
    throw new Error(`Unknown or incomplete argument: ${token}`);
  }

  return args;
}

function escapeHtml(value) {
  return String(value)
    .replaceAll("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll('"', "&quot;");
}

function formatMs(value) {
  return `${Number(value).toFixed(3)} ms`;
}

function formatCount(value) {
  return new Intl.NumberFormat("en-US").format(value);
}

function maxOf(values, fallback = 1) {
  const numeric = values.filter((value) => Number.isFinite(value));
  if (numeric.length === 0) {
    return fallback;
  }
  return Math.max(...numeric);
}

function buildGroupedBarChart({
  title,
  categories,
  series,
  colors,
  hint = "Lower is better.",
  height = 360,
}) {
  if (categories.length === 0) {
    return `<section class="chart-card"><h3>${escapeHtml(title)}</h3><p>No benchmark data.</p></section>`;
  }

  const width = 1200;
  const margin = { top: 44, right: 30, bottom: 110, left: 72 };
  const plotWidth = width - margin.left - margin.right;
  const plotHeight = height - margin.top - margin.bottom;
  const bandWidth = plotWidth / categories.length;
  const clusterWidth = bandWidth * 0.72;
  const barWidth = clusterWidth / series.length;
  const maxValue = maxOf(series.flatMap((entry) => entry.values), 1) * 1.15;
  const ticks = 5;

  const bars = [];
  const labels = [];
  const grid = [];

  for (let tick = 0; tick <= ticks; tick += 1) {
    const value = (maxValue / ticks) * tick;
    const y = margin.top + plotHeight - (value / maxValue) * plotHeight;
    grid.push(
      `<line x1="${margin.left}" y1="${y.toFixed(2)}" x2="${width - margin.right}" y2="${y.toFixed(2)}" class="grid-line" />` +
        `<text x="${margin.left - 10}" y="${(y + 4).toFixed(2)}" class="axis-text axis-text-right">${value.toFixed(0)}</text>`,
    );
  }

  categories.forEach((category, categoryIndex) => {
    const clusterX = margin.left + categoryIndex * bandWidth + (bandWidth - clusterWidth) / 2;
    labels.push(
      `<text x="${(clusterX + clusterWidth / 2).toFixed(2)}" y="${height - 48}" class="axis-text axis-label" transform="rotate(-24 ${(clusterX + clusterWidth / 2).toFixed(2)} ${height - 48})">${escapeHtml(category)}</text>`,
    );

    series.forEach((entry, seriesIndex) => {
      const value = entry.values[categoryIndex];
      const barHeight = (value / maxValue) * plotHeight;
      const x = clusterX + seriesIndex * barWidth;
      const y = margin.top + plotHeight - barHeight;
      bars.push(
        `<rect x="${x.toFixed(2)}" y="${y.toFixed(2)}" width="${(barWidth - 4).toFixed(2)}" height="${barHeight.toFixed(2)}" rx="6" fill="${colors[seriesIndex]}" />`,
      );
    });
  });

  const legend = series
    .map(
      (entry, index) => `
        <div class="legend-item">
          <span class="legend-swatch" style="background:${colors[index]}"></span>
          <span>${escapeHtml(entry.label)}</span>
        </div>`,
    )
    .join("");

  return `
    <section class="chart-card">
      <div class="chart-head">
        <div>
          <h3>${escapeHtml(title)}</h3>
          <p class="chart-note">${escapeHtml(hint)}</p>
        </div>
        <div class="legend">${legend}</div>
      </div>
      <svg viewBox="0 0 ${width} ${height}" role="img" aria-label="${escapeHtml(title)}">
        <rect x="0" y="0" width="${width}" height="${height}" fill="transparent"></rect>
        ${grid.join("")}
        <line x1="${margin.left}" y1="${margin.top + plotHeight}" x2="${width - margin.right}" y2="${margin.top + plotHeight}" class="axis-line" />
        <line x1="${margin.left}" y1="${margin.top}" x2="${margin.left}" y2="${margin.top + plotHeight}" class="axis-line" />
        ${bars.join("")}
        ${labels.join("")}
      </svg>
    </section>`;
}

function buildDeltaChart({
  title,
  categories,
  values,
  positiveColor,
  negativeColor,
  neutralColor,
  hint = "Lower is better. More negative values favor Athena Auth.",
  height = 320,
}) {
  if (categories.length === 0) {
    return `<section class="chart-card"><h3>${escapeHtml(title)}</h3><p>No benchmark data.</p></section>`;
  }

  const width = 1200;
  const margin = { top: 32, right: 30, bottom: 110, left: 72 };
  const plotWidth = width - margin.left - margin.right;
  const plotHeight = height - margin.top - margin.bottom;
  const bandWidth = plotWidth / categories.length;
  const barWidth = bandWidth * 0.55;
  const minValue = Math.min(0, ...values);
  const maxValue = Math.max(0, ...values);
  const span = Math.max(maxValue - minValue, 1);
  const zeroY = margin.top + plotHeight - ((0 - minValue) / span) * plotHeight;
  const ticks = 5;
  const bars = [];
  const labels = [];
  const grid = [];

  for (let tick = 0; tick <= ticks; tick += 1) {
    const value = minValue + (span / ticks) * tick;
    const y = margin.top + plotHeight - ((value - minValue) / span) * plotHeight;
    grid.push(
      `<line x1="${margin.left}" y1="${y.toFixed(2)}" x2="${width - margin.right}" y2="${y.toFixed(2)}" class="grid-line" />` +
        `<text x="${margin.left - 10}" y="${(y + 4).toFixed(2)}" class="axis-text axis-text-right">${value.toFixed(0)}</text>`,
    );
  }

  categories.forEach((category, index) => {
    const value = values[index];
    const centerX = margin.left + index * bandWidth + bandWidth / 2;
    const x = centerX - barWidth / 2;
    const y = value >= 0 ? zeroY - ((value / span) * plotHeight) : zeroY;
    const heightValue = Math.abs((value / span) * plotHeight);
    const fill =
      value > 0 ? positiveColor : value < 0 ? negativeColor : neutralColor;
    bars.push(
      `<rect x="${x.toFixed(2)}" y="${y.toFixed(2)}" width="${barWidth.toFixed(2)}" height="${Math.max(heightValue, 2).toFixed(2)}" rx="6" fill="${fill}" />`,
    );
    labels.push(
      `<text x="${centerX.toFixed(2)}" y="${height - 48}" class="axis-text axis-label" transform="rotate(-24 ${centerX.toFixed(2)} ${height - 48})">${escapeHtml(category)}</text>`,
    );
  });

  return `
    <section class="chart-card">
      <div class="chart-head">
        <div>
          <h3>${escapeHtml(title)}</h3>
          <p class="chart-note">${escapeHtml(hint)}</p>
        </div>
        <div class="legend">
          <div class="legend-item">
            <span class="legend-swatch" style="background:${negativeColor}"></span>
            <span>Athena faster</span>
          </div>
          <div class="legend-item">
            <span class="legend-swatch" style="background:${positiveColor}"></span>
            <span>Better Auth faster</span>
          </div>
        </div>
      </div>
      <svg viewBox="0 0 ${width} ${height}" role="img" aria-label="${escapeHtml(title)}">
        ${grid.join("")}
        <line x1="${margin.left}" y1="${zeroY.toFixed(2)}" x2="${width - margin.right}" y2="${zeroY.toFixed(2)}" class="axis-line axis-zero" />
        <line x1="${margin.left}" y1="${margin.top}" x2="${margin.left}" y2="${margin.top + plotHeight}" class="axis-line" />
        ${bars.join("")}
        ${labels.join("")}
      </svg>
    </section>`;
}

function buildRunGrid(scenarios) {
  const cards = scenarios
    .filter((scenario) => scenario.runs.length > 0)
    .map((scenario) => {
      const rows = scenario.runs
        .map((run) => {
          if (!run.metrics) {
            return `<tr><td>${run.run_index + 1}</td><td colspan="4">${escapeHtml(run.skip_reason ?? "skipped")}</td></tr>`;
          }
          return `<tr>
            <td>${run.run_index + 1}</td>
            <td>${run.samples}</td>
            <td>${formatMs(run.metrics.rust_avg_ms)}</td>
            <td>${formatMs(run.metrics.reference_avg_ms)}</td>
            <td>${formatMs(run.metrics.delta_avg_ms)}</td>
          </tr>`;
        })
        .join("");

      return `
        <article class="run-card">
          <h4>${escapeHtml(scenario.id)} ${escapeHtml(scenario.label)}</h4>
          <p>${scenario.completed_runs}/${scenario.suite_repeats} runs completed, ${scenario.total_samples} measured samples.</p>
          <table>
            <thead>
              <tr>
                <th>Run</th>
                <th>Samples</th>
                <th>Athena Avg</th>
                <th>Better Avg</th>
                <th>Delta</th>
              </tr>
            </thead>
            <tbody>${rows}</tbody>
          </table>
        </article>`;
    })
    .join("");

  return `
    <section class="panel">
      <div class="panel-head">
        <h2>Per-Run Breakdown</h2>
        <p>Repeated runs expose variance and partial skips instead of hiding them behind a single mean.</p>
      </div>
      <div class="run-grid">${cards}</div>
    </section>`;
}

function buildFlowDiagram() {
  return `
    <section class="panel">
      <div class="panel-head">
        <div>
          <h2>Benchmark Flow</h2>
          <p>The generated report follows the same execution path as the WSL runner.</p>
        </div>
        <div class="legend">
          <div class="legend-item">
            <span class="legend-swatch" style="background:var(--athena)"></span>
            <span>Athena path</span>
          </div>
          <div class="legend-item">
            <span class="legend-swatch" style="background:var(--better)"></span>
            <span>Better Auth reference</span>
          </div>
        </div>
      </div>
      <svg class="flow-diagram" viewBox="0 0 1080 220" role="img" aria-label="Benchmark flow diagram">
        <defs>
          <marker id="arrow" markerWidth="10" markerHeight="10" refX="9" refY="5" orient="auto-start-reverse">
            <path d="M0,0 L10,5 L0,10 z" fill="#667085"></path>
          </marker>
        </defs>
        <rect x="20" y="40" width="180" height="84" rx="18" class="flow-box flow-athena"></rect>
        <text x="110" y="75" text-anchor="middle" class="flow-title">WSL runner</text>
        <text x="110" y="100" text-anchor="middle" class="flow-text">boot Linux Node</text>

        <rect x="250" y="40" width="180" height="84" rx="18" class="flow-box flow-better"></rect>
        <text x="340" y="75" text-anchor="middle" class="flow-title">Reference server</text>
        <text x="340" y="100" text-anchor="middle" class="flow-text">Better Auth + SQLite</text>

        <rect x="480" y="40" width="180" height="84" rx="18" class="flow-box flow-athena"></rect>
        <text x="570" y="75" text-anchor="middle" class="flow-title">Athena Auth</text>
        <text x="570" y="100" text-anchor="middle" class="flow-text">Axum in-process router</text>

        <rect x="710" y="40" width="180" height="84" rx="18" class="flow-box flow-athena"></rect>
        <text x="800" y="75" text-anchor="middle" class="flow-title">Repeated benchmark</text>
        <text x="800" y="100" text-anchor="middle" class="flow-text">warmup + measured runs</text>

        <rect x="940" y="40" width="120" height="84" rx="18" class="flow-box flow-athena"></rect>
        <text x="1000" y="75" text-anchor="middle" class="flow-title">Artifacts</text>
        <text x="1000" y="100" text-anchor="middle" class="flow-text">JSON + MD + HTML</text>

        <line x1="200" y1="82" x2="250" y2="82" class="flow-line"></line>
        <line x1="430" y1="82" x2="480" y2="82" class="flow-line"></line>
        <line x1="660" y1="82" x2="710" y2="82" class="flow-line"></line>
        <line x1="890" y1="82" x2="940" y2="82" class="flow-line"></line>
      </svg>
    </section>`;
}

function buildScenarioTable(scenarios) {
  const rows = scenarios
    .map((scenario) => {
      const metrics = scenario.aggregate_metrics;
      if (!metrics) {
        return `<tr>
          <td>${escapeHtml(scenario.id)}</td>
          <td>${escapeHtml(scenario.label)}</td>
          <td>${scenario.completed_runs}/${scenario.suite_repeats}</td>
          <td>0</td>
          <td colspan="5">${escapeHtml(scenario.skip_reasons.join("; ") || "No successful runs")}</td>
        </tr>`;
      }
      return `<tr>
        <td>${escapeHtml(scenario.id)}</td>
        <td>${escapeHtml(scenario.label)}</td>
        <td>${scenario.completed_runs}/${scenario.suite_repeats}</td>
        <td>${scenario.total_samples}</td>
        <td>${formatMs(metrics.rust_avg_ms)}</td>
        <td>${formatMs(metrics.reference_avg_ms)}</td>
        <td>${formatMs(metrics.delta_avg_ms)}</td>
        <td>${formatMs(metrics.rust_p95_ms)}</td>
        <td>${formatMs(metrics.reference_p95_ms)}</td>
      </tr>`;
    })
    .join("");

  return `
    <section class="panel">
      <div class="panel-head">
        <h2>Scenario Summary</h2>
        <p>Aggregate metrics are computed across every successful repeated run.</p>
      </div>
      <table class="summary-table">
        <thead>
          <tr>
            <th>ID</th>
            <th>Endpoint</th>
            <th>Runs</th>
            <th>Samples</th>
            <th>Athena Avg</th>
            <th>Better Avg</th>
            <th>Delta</th>
            <th>Athena p95</th>
            <th>Better p95</th>
          </tr>
        </thead>
        <tbody>${rows}</tbody>
      </table>
    </section>`;
}

function buildHtml(report) {
  const completeScenarios = report.scenarios.filter((scenario) => scenario.aggregate_metrics);
  const endpointLabels = completeScenarios.map((scenario) => scenario.id);
  const athenaAvg = completeScenarios.map((scenario) => scenario.aggregate_metrics.rust_avg_ms);
  const betterAvg = completeScenarios.map((scenario) => scenario.aggregate_metrics.reference_avg_ms);
  const athenaP95 = completeScenarios.map((scenario) => scenario.aggregate_metrics.rust_p95_ms);
  const betterP95 = completeScenarios.map((scenario) => scenario.aggregate_metrics.reference_p95_ms);
  const deltas = completeScenarios.map((scenario) => scenario.aggregate_metrics.delta_avg_ms);
  const totalSamples = report.scenarios.reduce((sum, scenario) => sum + scenario.total_samples, 0);
  const totalCompletedRuns = report.scenarios.reduce((sum, scenario) => sum + scenario.completed_runs, 0);
  const totalSkippedRuns = report.scenarios.reduce((sum, scenario) => sum + scenario.skip_reasons.length, 0);

  return `<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>${escapeHtml(report.title)}</title>
    <style>
      :root {
        --athena: ${report.palette.athena_hex};
        --better: ${report.palette.better_hex};
        --delta-positive: ${report.palette.delta_positive_hex};
        --delta-negative: ${report.palette.delta_negative_hex};
        --neutral: ${report.palette.neutral_hex};
        --bg: ${report.palette.background_hex};
        --panel: #ffffff;
        --ink: #0f172a;
        --muted: #475467;
        --line: #d0d5dd;
      }
      * { box-sizing: border-box; }
      body {
        margin: 0;
        font-family: "Segoe UI", "Helvetica Neue", Arial, sans-serif;
        color: var(--ink);
        background:
          radial-gradient(circle at top left, rgba(52, 64, 213, 0.10), transparent 28%),
          linear-gradient(180deg, #eef2ff 0%, var(--bg) 30%, #ffffff 100%);
      }
      main { max-width: 1440px; margin: 0 auto; padding: 40px 28px 80px; }
      .hero {
        display: grid;
        grid-template-columns: 1.8fr 1fr;
        gap: 24px;
        margin-bottom: 28px;
      }
      .hero-card, .panel, .chart-card, .metric-card, .run-card {
        background: var(--panel);
        border: 1px solid rgba(15, 23, 42, 0.08);
        border-radius: 24px;
        box-shadow: 0 20px 50px rgba(15, 23, 42, 0.08);
      }
      .hero-card { padding: 28px; }
      .hero-card h1 { margin: 0 0 12px; font-size: 2.3rem; line-height: 1.05; }
      .hero-card p, .panel-head p, .metric-card p, .run-card p { color: var(--muted); }
      .hero-meta { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 18px; }
      .hero-meta span {
        padding: 10px 14px;
        border-radius: 999px;
        background: rgba(52, 64, 213, 0.08);
        color: var(--athena);
        font-weight: 600;
      }
      .metrics {
        display: grid;
        grid-template-columns: repeat(4, minmax(0, 1fr));
        gap: 16px;
        margin-bottom: 24px;
      }
      .metric-card { padding: 18px 20px; }
      .metric-card h2 { margin: 0; font-size: 2rem; }
      .metric-card p { margin: 8px 0 0; }
      .panel, .chart-card { padding: 24px; margin-bottom: 24px; }
      .panel-head, .chart-head { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; margin-bottom: 16px; }
      .panel-head h2, .chart-head h3 { margin: 0; }
      .chart-note { margin: 6px 0 0; color: var(--muted); font-size: 0.95rem; }
      .legend { display: flex; gap: 16px; flex-wrap: wrap; color: var(--muted); }
      .legend-item { display: flex; align-items: center; gap: 8px; }
      .legend-swatch { width: 12px; height: 12px; border-radius: 999px; display: inline-block; }
      .chart-grid { display: grid; grid-template-columns: 1fr; gap: 24px; }
      svg { width: 100%; height: auto; display: block; }
      .grid-line { stroke: rgba(152, 162, 179, 0.32); stroke-width: 1; }
      .axis-line { stroke: #667085; stroke-width: 1.5; }
      .axis-zero { stroke-dasharray: 6 6; }
      .axis-text { fill: #667085; font-size: 14px; font-family: inherit; }
      .axis-text-right { text-anchor: end; }
      .axis-label { text-anchor: middle; }
      table { width: 100%; border-collapse: collapse; }
      th, td { text-align: left; padding: 12px 10px; border-bottom: 1px solid #eaecf0; vertical-align: top; }
      th { color: var(--muted); font-size: 0.92rem; }
      .run-grid {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
        gap: 18px;
      }
      .run-card { padding: 18px; }
      .run-card h4 { margin: 0 0 8px; }
      .flow-diagram .flow-box { stroke: rgba(15, 23, 42, 0.08); stroke-width: 1.5; }
      .flow-diagram .flow-athena { fill: rgba(52, 64, 213, 0.12); }
      .flow-diagram .flow-better { fill: rgba(253, 186, 116, 0.28); }
      .flow-diagram .flow-neutral { fill: rgba(107, 114, 128, 0.12); }
      .flow-diagram .flow-line { stroke: #667085; stroke-width: 2.5; marker-end: url(#arrow); }
      .flow-title { font: 700 18px "Segoe UI", sans-serif; fill: #111827; }
      .flow-text { font: 14px "Segoe UI", sans-serif; fill: #475467; }
      code {
        background: rgba(15, 23, 42, 0.05);
        padding: 2px 6px;
        border-radius: 6px;
      }
      @media (max-width: 980px) {
        .hero { grid-template-columns: 1fr; }
        .metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
      }
      @media (max-width: 640px) {
        main { padding: 24px 16px 60px; }
        .metrics { grid-template-columns: 1fr; }
        .hero-card h1 { font-size: 1.8rem; }
      }
    </style>
  </head>
  <body>
    <main>
      <section class="hero">
        <article class="hero-card">
          <p>Generated ${escapeHtml(report.generated_at)}</p>
          <h1>${escapeHtml(report.title)}</h1>
          <p>
            Athena Auth is rendered in indigo, Better Auth in light orange. This dashboard is generated
            from repeated WSL benchmark runs against the in-process Axum stack and the Better Auth
            compatibility server.
          </p>
          <div class="hero-meta">
            <span>${report.iterations_per_run} measured iterations</span>
            <span>${report.warmup_iterations} warmups</span>
            <span>${report.suite_repeats} repeated runs</span>
            <span>Argon2 ${report.argon2_profile.memory_cost}/${report.argon2_profile.time_cost}/${report.argon2_profile.parallelism}</span>
          </div>
        </article>
        <article class="hero-card">
          <h2>Artifact Set</h2>
          <p><code>${escapeHtml(report.markdown_report_path)}</code></p>
          <p><code>${escapeHtml(report.json_report_path)}</code></p>
          <p><code>${escapeHtml(report.html_report_path)}</code></p>
          <p>Reference origin: <code>${escapeHtml(report.reference_origin)}</code></p>
        </article>
      </section>

      <section class="metrics">
        <article class="metric-card">
          <h2>${formatCount(report.scenarios.length)}</h2>
          <p>scenarios configured</p>
        </article>
        <article class="metric-card">
          <h2>${formatCount(totalCompletedRuns)}</h2>
          <p>successful endpoint runs</p>
        </article>
        <article class="metric-card">
          <h2>${formatCount(totalSamples)}</h2>
          <p>measured samples across successful runs</p>
        </article>
        <article class="metric-card">
          <h2>${formatCount(totalSkippedRuns)}</h2>
          <p>skipped runs captured in the report</p>
        </article>
      </section>

      <div class="chart-grid">
        ${buildGroupedBarChart({
          title: "Average latency by endpoint",
          categories: endpointLabels,
          series: [
            { label: "Athena Auth", values: athenaAvg },
            { label: "Better Auth", values: betterAvg },
          ],
          colors: [report.palette.athena_hex, report.palette.better_hex],
          hint: "Lower is better.",
        })}
        ${buildGroupedBarChart({
          title: "p95 latency by endpoint",
          categories: endpointLabels,
          series: [
            { label: "Athena Auth p95", values: athenaP95 },
            { label: "Better Auth p95", values: betterP95 },
          ],
          colors: [report.palette.athena_hex, report.palette.better_hex],
          hint: "Lower is better.",
        })}
        ${buildDeltaChart({
          title: "Average latency delta (Athena Auth - Better Auth)",
          categories: endpointLabels,
          values: deltas,
          positiveColor: report.palette.delta_positive_hex,
          negativeColor: report.palette.delta_negative_hex,
          neutralColor: report.palette.neutral_hex,
          hint: "Lower is better. Negative indigo bars mean Athena Auth is faster.",
        })}
      </div>

      ${buildFlowDiagram()}
      ${buildScenarioTable(report.scenarios)}
      ${buildRunGrid(report.scenarios)}
    </main>
  </body>
</html>`;
}

function main() {
  const args = parseArgs(process.argv);
  const inputPath = path.resolve(args.input);
  const outputPath = path.resolve(args.output);
  const report = JSON.parse(fs.readFileSync(inputPath, "utf8"));
  const html = buildHtml(report);
  fs.mkdirSync(path.dirname(outputPath), { recursive: true });
  fs.writeFileSync(outputPath, html, "utf8");
  console.error(`[dual-bench] html dashboard written to ${path.relative(process.cwd(), outputPath)}`);
}

main();
