#!/usr/bin/env python3
"""Tiny live dashboard for Gemma 4 baseline-vs-MTP benchmark runs."""

from __future__ import annotations

import argparse
import json
import mimetypes
import os
import re
import time
import urllib.error
import urllib.parse
import urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from typing import Any


METRIC_RE = re.compile(r"^(sglang:\w+)(?:\{([^}]*)\})?\s+([\d.eE+-]+)")


HTML = r"""<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Gemma 4 MTP Derby</title>
  <style>
    :root {
      --track: #07110e;
      --rail: rgba(233, 238, 225, 0.18);
      --ink: #f2f4eb;
      --muted: rgba(242, 244, 235, 0.64);
      --soft: rgba(242, 244, 235, 0.09);
      --line: rgba(242, 244, 235, 0.16);
      --green: #66f0a3;
      --amber: #f5c84c;
      --red: #ff6b5a;
      --blue: #8ec5ff;
      --shadow: rgba(0, 0, 0, 0.34);
    }

    * { box-sizing: border-box; }

    body {
      margin: 0;
      min-height: 100vh;
      background:
        linear-gradient(rgba(242, 244, 235, 0.035) 1px, transparent 1px),
        linear-gradient(90deg, rgba(242, 244, 235, 0.035) 1px, transparent 1px),
        var(--track);
      background-size: 48px 48px;
      color: var(--ink);
      font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      letter-spacing: 0;
    }

    main {
      width: min(1540px, calc(100% - 32px));
      margin: 0 auto;
      padding: 22px 0 40px;
    }

    .topline {
      display: grid;
      grid-template-columns: minmax(0, 1fr) auto;
      gap: 18px;
      align-items: end;
      padding: 20px 0 18px;
      border-bottom: 1px solid var(--line);
    }

    h1 {
      margin: 0;
      font-size: clamp(2.5rem, 7vw, 7.2rem);
      line-height: 0.9;
      font-weight: 800;
      letter-spacing: 0;
    }

    .subhead {
      margin: 12px 0 0;
      max-width: 74ch;
      color: var(--muted);
      font-size: 1rem;
      line-height: 1.55;
    }

    .status-pill {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      gap: 10px;
      min-height: 42px;
      padding: 10px 14px;
      border: 1px solid var(--line);
      color: var(--muted);
      text-transform: uppercase;
      font-size: 0.78rem;
      letter-spacing: 0.12em;
      white-space: nowrap;
    }

    .status-pill span {
      width: 10px;
      height: 10px;
      background: var(--red);
      border-radius: 50%;
    }

    .status-pill.live span {
      background: var(--green);
      box-shadow: 0 0 18px rgba(102, 240, 163, 0.8);
      animation: pulse 1100ms ease-in-out infinite;
    }

    .board {
      display: grid;
      grid-template-columns: minmax(0, 1.2fr) minmax(340px, 0.58fr);
      gap: 16px;
      margin-top: 16px;
    }

    .panel {
      min-width: 0;
      border: 1px solid var(--line);
      background: rgba(3, 8, 7, 0.78);
      box-shadow: 0 22px 70px var(--shadow);
    }

    .panel-head {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 14px;
      min-height: 48px;
      padding: 12px 14px;
      border-bottom: 1px solid var(--line);
      color: var(--muted);
      text-transform: uppercase;
      font-size: 0.76rem;
      letter-spacing: 0.12em;
    }

    .race {
      display: grid;
      gap: 0;
    }

    .lane {
      display: grid;
      grid-template-columns: 170px minmax(0, 1fr) 116px;
      align-items: center;
      min-height: 118px;
      border-bottom: 1px solid var(--rail);
      overflow: hidden;
    }

    .lane:last-child { border-bottom: 0; }

    .runner {
      display: grid;
      gap: 8px;
      padding: 16px;
      border-right: 1px solid var(--rail);
    }

    .runner strong {
      font-size: 1.08rem;
      line-height: 1;
    }

    .runner span {
      color: var(--muted);
      font-size: 0.78rem;
      text-transform: uppercase;
      letter-spacing: 0.12em;
    }

    .track {
      position: relative;
      height: 100%;
      min-height: 118px;
      overflow: hidden;
      background:
        repeating-linear-gradient(
          90deg,
          transparent 0,
          transparent 58px,
          rgba(242, 244, 235, 0.055) 58px,
          rgba(242, 244, 235, 0.055) 60px
        );
    }

    .track::after {
      content: "";
      position: absolute;
      inset: 0;
      background: linear-gradient(90deg, transparent, rgba(245, 200, 76, 0.08), transparent);
      transform: translateX(-100%);
      animation: sweep 3600ms linear infinite;
    }

    .bar {
      position: absolute;
      left: 0;
      top: 50%;
      height: 44px;
      width: var(--w, 4%);
      transform: translateY(-50%);
      background: linear-gradient(90deg, var(--color), rgba(242, 244, 235, 0.92));
      transition: width 650ms cubic-bezier(.2, .9, .2, 1);
    }

    .bar::after {
      content: "";
      position: absolute;
      right: -10px;
      top: 50%;
      width: 20px;
      height: 20px;
      border: 2px solid var(--ink);
      background: var(--color);
      transform: translateY(-50%) rotate(45deg);
    }

    .metric {
      display: grid;
      justify-items: end;
      gap: 6px;
      padding: 16px;
      border-left: 1px solid var(--rail);
      text-align: right;
    }

    .metric strong {
      font-size: 1.6rem;
      line-height: 1;
    }

    .metric span {
      color: var(--muted);
      font-size: 0.74rem;
      text-transform: uppercase;
      letter-spacing: 0.1em;
    }

    .metrics-grid {
      display: grid;
      grid-template-columns: repeat(3, minmax(0, 1fr));
      border-top: 1px solid var(--line);
    }

    .stat {
      display: grid;
      gap: 8px;
      min-height: 104px;
      padding: 14px;
      border-right: 1px solid var(--line);
      border-bottom: 1px solid var(--line);
    }

    .stat:nth-child(3n) { border-right: 0; }
    .stat:nth-last-child(-n+3) { border-bottom: 0; }

    .stat span {
      color: var(--muted);
      font-size: 0.72rem;
      letter-spacing: 0.12em;
      text-transform: uppercase;
    }

    .stat strong {
      align-self: end;
      font-size: clamp(1.45rem, 3vw, 2.25rem);
      line-height: 1;
      overflow-wrap: anywhere;
    }

    .tables {
      display: grid;
      grid-template-columns: minmax(0, 1fr);
      gap: 16px;
      margin-top: 16px;
    }

    table {
      width: 100%;
      border-collapse: collapse;
      table-layout: fixed;
      font-size: 0.92rem;
    }

    th, td {
      padding: 13px 12px;
      border-bottom: 1px solid var(--line);
      text-align: right;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }

    th:first-child, td:first-child {
      text-align: left;
    }

    th {
      color: var(--muted);
      font-size: 0.7rem;
      letter-spacing: 0.11em;
      text-transform: uppercase;
      font-weight: 600;
    }

    tbody tr:hover {
      background: rgba(242, 244, 235, 0.055);
    }

    .good { color: var(--green); }
    .warn { color: var(--amber); }
    .bad { color: var(--red); }
    .blue { color: var(--blue); }

    .log {
      max-height: 290px;
      overflow: auto;
      margin: 0;
      padding: 14px;
      color: rgba(242, 244, 235, 0.72);
      font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
      font-size: 0.78rem;
      line-height: 1.5;
      white-space: pre-wrap;
    }

    .empty {
      padding: 20px 14px;
      color: var(--muted);
      line-height: 1.5;
    }

    @keyframes pulse {
      0%, 100% { opacity: 0.65; transform: scale(0.9); }
      50% { opacity: 1; transform: scale(1.12); }
    }

    @keyframes sweep {
      to { transform: translateX(100%); }
    }

    @media (max-width: 1040px) {
      .topline,
      .board {
        grid-template-columns: 1fr;
      }

      .status-pill {
        justify-self: start;
      }
    }

    @media (max-width: 720px) {
      main {
        width: min(100% - 20px, 1540px);
        padding-top: 12px;
      }

      .lane {
        grid-template-columns: 1fr;
      }

      .runner,
      .metric {
        border: 0;
        justify-items: start;
        text-align: left;
      }

      .track {
        min-height: 72px;
        border-top: 1px solid var(--rail);
        border-bottom: 1px solid var(--rail);
      }

      .metrics-grid {
        grid-template-columns: 1fr 1fr;
      }

      .stat:nth-child(3n) { border-right: 1px solid var(--line); }
      .stat:nth-child(2n) { border-right: 0; }
      .stat:nth-last-child(-n+3) { border-bottom: 1px solid var(--line); }
      .stat:nth-last-child(-n+2) { border-bottom: 0; }
    }
  </style>
</head>
<body>
  <main>
    <section class="topline">
      <div>
        <h1>Gemma 4 MTP Derby</h1>
        <p class="subhead">Baseline and MTP run the same matrix, one clean server at a time. The track shows live throughput while the steward table keeps the final JSON honest.</p>
      </div>
      <div id="status" class="status-pill"><span></span><b>Waiting</b></div>
    </section>

    <section class="board">
      <div class="panel">
        <div class="panel-head">
          <span>Race Track</span>
          <span id="cellLabel">No active cell</span>
        </div>
        <div class="race" id="race"></div>
      </div>

      <div class="panel">
        <div class="panel-head">
          <span>Live Board</span>
          <span id="updated">--</span>
        </div>
        <div class="metrics-grid" id="metrics"></div>
      </div>
    </section>

    <section class="tables">
      <div class="panel">
        <div class="panel-head">
          <span>Photo Finish</span>
          <span id="winner">Waiting for results</span>
        </div>
        <div id="tableWrap"></div>
      </div>

      <div class="panel">
        <div class="panel-head">
          <span>Last Log Lines</span>
          <span id="logLabel">baseline / mtp</span>
        </div>
        <pre class="log" id="log"></pre>
      </div>
    </section>
  </main>

  <script>
    const fmt = new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 });

    function pct(value) {
      if (!Number.isFinite(value)) return "0%";
      return `${Math.round(value * 100)}%`;
    }

    function tps(value) {
      if (!Number.isFinite(value) || value <= 0) return "--";
      return fmt.format(value);
    }

    function latestResult(doc) {
      const results = doc?.results || [];
      const usable = results.filter((r) => r.aggregate_tps > 0);
      if (!usable.length) return 0;
      return usable[usable.length - 1].aggregate_tps;
    }

    function bestResult(doc) {
      const rows = doc?.results || [];
      return rows.reduce((best, row) => Math.max(best, row.aggregate_tps || 0), 0);
    }

    function liveTps(live, finalDoc) {
      return live?.cell_running ? live.cell_live_tps : latestResult(finalDoc) || bestResult(finalDoc);
    }

    function renderRace(snapshot) {
      const baseline = snapshot.live?.baseline;
      const mtp = snapshot.live?.mtp_topk1;
      const baselineDoc = snapshot.results?.baseline;
      const mtpDoc = snapshot.results?.mtp_topk1;
      const baselineValue = liveTps(baseline, baselineDoc);
      const mtpValue = liveTps(mtp, mtpDoc);
      const max = Math.max(baselineValue, mtpValue, 1);
      const lanes = [
        { name: "Baseline", detail: "Gemma 4 target only", value: baselineValue, color: "var(--blue)", live: baseline?.cell_running },
        { name: "MTP", detail: "Gemma 4 assistant drafter", value: mtpValue, color: "var(--green)", live: mtp?.cell_running },
      ];

      document.getElementById("race").innerHTML = lanes.map((lane) => {
        const width = Math.max(3, Math.min(96, (lane.value / max) * 92));
        return `
          <div class="lane">
            <div class="runner">
              <strong>${lane.name}</strong>
              <span>${lane.live ? "Running now" : lane.detail}</span>
            </div>
            <div class="track">
              <div class="bar" style="--w:${width}%;--color:${lane.color}"></div>
            </div>
            <div class="metric">
              <strong>${tps(lane.value)}</strong>
              <span>tok/s</span>
            </div>
          </div>`;
      }).join("");
    }

    function renderMetrics(snapshot) {
      const active = snapshot.active_live || {};
      const server = snapshot.metrics || active.server || {};
      const progress = active.total_tests ? `${active.completed_tests || 0}/${active.total_tests}` : "--";
      const stats = [
        ["Throughput", `${tps(server["sglang:gen_throughput"] ?? server.gen_throughput)} tok/s`, "good"],
        ["Accept Rate", pct(server["sglang:spec_accept_rate"] ?? server.spec_accept_rate), "good"],
        ["Accept Len", fmt.format(server["sglang:spec_accept_length"] ?? server.spec_accept_length ?? 0), "warn"],
        ["Utilization", pct(server["sglang:utilization"] ?? server.utilization), "blue"],
        ["Running", String(server["sglang:num_running_reqs"] ?? server.running_reqs ?? 0), ""],
        ["Progress", progress, ""],
      ];

      document.getElementById("metrics").innerHTML = stats.map(([label, value, cls]) => `
        <div class="stat">
          <span>${label}</span>
          <strong class="${cls}">${value}</strong>
        </div>
      `).join("");

      const activeLabel = active.cell_running
        ? `${active.phase || "run"} C=${active.current_concurrency || 1}, ctx=${active.current_context || 0}`
        : "No active cell";
      document.getElementById("cellLabel").textContent = activeLabel;
    }

    function renderTable(snapshot) {
      const rows = snapshot.summary?.comparison || [];
      const wrap = document.getElementById("tableWrap");
      if (!rows.length) {
        wrap.innerHTML = '<div class="empty">No comparison rows yet. Start the benchmark and this will fill in after both runners cross the line.</div>';
        document.getElementById("winner").textContent = "Waiting for results";
        return;
      }

      const avg = rows.reduce((sum, row) => sum + (row.speedup || 0), 0) / rows.length;
      const best = rows.reduce((winner, row) => (row.speedup || 0) > (winner.speedup || 0) ? row : winner, rows[0]);
      document.getElementById("winner").textContent = `Avg ${avg.toFixed(2)}x, best ${best.speedup.toFixed(2)}x`;

      wrap.innerHTML = `
        <table>
          <thead>
            <tr>
              <th>Context</th>
              <th>Conc</th>
              <th>Baseline</th>
              <th>MTP</th>
              <th>Speedup</th>
              <th>Accept</th>
              <th>Wall</th>
            </tr>
          </thead>
          <tbody>
            ${rows.map((row) => {
              const cls = row.speedup >= 1.15 ? "good" : row.speedup >= 1 ? "warn" : "bad";
              return `
                <tr>
                  <td>${row.context_tokens}</td>
                  <td>${row.concurrency}</td>
                  <td>${fmt.format(row.baseline_tps)}</td>
                  <td>${fmt.format(row.mtp_tps)}</td>
                  <td class="${cls}">${row.speedup.toFixed(2)}x</td>
                  <td>${pct(row.mtp_accept_rate)}</td>
                  <td>${fmt.format(row.baseline_wall_time || 0)}s / ${fmt.format(row.mtp_wall_time || 0)}s</td>
                </tr>`;
            }).join("")}
          </tbody>
        </table>`;
    }

    function renderLog(snapshot) {
      const active = snapshot.active_case || "baseline";
      const logs = snapshot.logs || {};
      document.getElementById("logLabel").textContent = active;
      document.getElementById("log").textContent = logs[active] || logs.baseline || logs.mtp_topk1 || "No logs yet.";
    }

    function renderStatus(snapshot) {
      const live = Boolean(snapshot.active_live?.cell_running);
      const node = document.getElementById("status");
      node.classList.toggle("live", live);
      node.querySelector("b").textContent = live ? "Live" : "Idle";
      document.getElementById("updated").textContent = snapshot.updated_at ? new Date(snapshot.updated_at).toLocaleTimeString() : "--";
    }

    async function tick() {
      try {
        const res = await fetch("/api/snapshot", { cache: "no-store" });
        const snapshot = await res.json();
        renderStatus(snapshot);
        renderRace(snapshot);
        renderMetrics(snapshot);
        renderTable(snapshot);
        renderLog(snapshot);
      } catch (err) {
        document.getElementById("status").querySelector("b").textContent = "Disconnected";
      }
    }

    tick();
    setInterval(tick, 1000);
  </script>
</body>
</html>
"""


def load_json(path: Path) -> Any:
    try:
        with path.open() as f:
            return json.load(f)
    except Exception:
        return None


def tail_text(path: Path, lines: int = 90) -> str:
    try:
        with path.open(errors="replace") as f:
            data = f.readlines()
        return "".join(data[-lines:])
    except Exception:
        return ""


def scrape_metrics(sglang_url: str) -> dict[str, float]:
    metrics: dict[str, float] = {}
    if not sglang_url:
        return metrics

    url = urllib.parse.urljoin(sglang_url.rstrip("/") + "/", "metrics")
    try:
        with urllib.request.urlopen(url, timeout=1.5) as resp:
            body = resp.read().decode("utf-8", errors="replace")
    except (urllib.error.URLError, TimeoutError, OSError):
        return metrics

    for line in body.splitlines():
        if line.startswith("#"):
            continue
        match = METRIC_RE.match(line)
        if not match:
            continue
        name, labels, value = match.group(1), match.group(2) or "", float(match.group(3))
        if "tp_rank=" in labels and 'tp_rank="0"' not in labels:
            continue
        if name not in metrics:
            metrics[name] = value
    return metrics


def live_is_recent(doc: dict[str, Any] | None, max_age_seconds: float = 12.0) -> bool:
    if not doc or not doc.get("cell_running"):
        return False
    try:
        updated = doc.get("updated_at", "")
        timestamp = time.mktime(time.strptime(updated[:19], "%Y-%m-%dT%H:%M:%S"))
        return (time.time() - timestamp) <= max_age_seconds
    except Exception:
        return True


def build_snapshot(results_dir: Path, sglang_url: str) -> dict[str, Any]:
    baseline_live = load_json(results_dir / "baseline.live.json")
    mtp_live = load_json(results_dir / "mtp_topk1.live.json")
    baseline_result = load_json(results_dir / "baseline.json")
    mtp_result = load_json(results_dir / "mtp_topk1.json")
    summary = load_json(results_dir / "summary.json")

    live_docs = {
        "baseline": baseline_live,
        "mtp_topk1": mtp_live,
    }
    active_case = "mtp_topk1" if live_is_recent(mtp_live) else "baseline"
    if not live_is_recent(live_docs.get(active_case)):
        active_case = "baseline" if live_is_recent(baseline_live) else "mtp_topk1" if mtp_live else "baseline"

    snapshot = {
        "updated_at": time.strftime("%Y-%m-%dT%H:%M:%S"),
        "results_dir": str(results_dir),
        "active_case": active_case,
        "active_live": live_docs.get(active_case) or {},
        "live": live_docs,
        "metrics": scrape_metrics(sglang_url),
        "results": {
            "baseline": baseline_result,
            "mtp_topk1": mtp_result,
        },
        "summary": summary,
        "logs": {
            "baseline": tail_text(results_dir / "logs" / "baseline.log"),
            "mtp_topk1": tail_text(results_dir / "logs" / "mtp_topk1.log"),
        },
    }
    return snapshot


class DashboardHandler(BaseHTTPRequestHandler):
    results_dir: Path
    sglang_url: str

    def log_message(self, fmt: str, *args: Any) -> None:
        return

    def send_body(self, status: int, body: bytes, content_type: str) -> None:
        self.send_response(status)
        self.send_header("Content-Type", content_type)
        self.send_header("Cache-Control", "no-store")
        self.end_headers()
        self.wfile.write(body)

    def do_GET(self) -> None:
        parsed = urllib.parse.urlparse(self.path)
        if parsed.path in {"/", "/index.html"}:
            self.send_body(200, HTML.encode("utf-8"), "text/html; charset=utf-8")
            return

        if parsed.path == "/api/snapshot":
            snapshot = build_snapshot(self.results_dir, self.sglang_url)
            self.send_body(
                200,
                json.dumps(snapshot).encode("utf-8"),
                "application/json; charset=utf-8",
            )
            return

        content_type = mimetypes.guess_type(parsed.path)[0] or "text/plain"
        self.send_body(404, b"not found", content_type)


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Gemma 4 MTP derby dashboard")
    parser.add_argument("--host", default="127.0.0.1")
    parser.add_argument("--port", type=int, default=7777)
    parser.add_argument("--results-dir", default="results/gemma4-mtp-sglang")
    parser.add_argument("--sglang-url", default="http://127.0.0.1:30000")
    return parser.parse_args()


def main() -> None:
    args = parse_args()
    handler = DashboardHandler
    handler.results_dir = Path(args.results_dir).resolve()
    handler.sglang_url = args.sglang_url
    os.makedirs(handler.results_dir, exist_ok=True)

    server = ThreadingHTTPServer((args.host, args.port), handler)
    print(f"Gemma 4 MTP Derby dashboard: http://{args.host}:{args.port}")
    print(f"results_dir={handler.results_dir}")
    print(f"sglang_url={handler.sglang_url}")
    server.serve_forever()


if __name__ == "__main__":
    main()
