"""
QPPG Monitoring Dashboard — FastAPI + plain HTML.

Adds /dashboard and /dashboard/api/* routes to an existing FastAPI app.
No extra dependencies: pure HTML, CSS, and vanilla JS with Chart.js from CDN.

Usage (standalone):
    llm-guard-kit dashboard --domain prod --port 8080

Usage (programmatic):
    from fastapi import FastAPI
    from qppg_service.dashboard import add_dashboard_routes
    from qppg_service.store import ChainStore

    app   = FastAPI()
    store = ChainStore()
    add_dashboard_routes(app, store)
"""

from __future__ import annotations

import time
from typing import TYPE_CHECKING

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

if TYPE_CHECKING:
    from .store import ChainStore


# ── HTML template ─────────────────────────────────────────────────────────────

_HTML = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>llm-guard-kit · {domain}</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
<style>
  :root {{
    --bg: #0f1117; --card: #1a1d27; --border: #2d3148;
    --text: #e2e8f0; --muted: #94a3b8; --accent: #6366f1;
    --green: #22c55e; --yellow: #eab308; --red: #ef4444;
  }}
  * {{ box-sizing: border-box; margin: 0; padding: 0; }}
  body {{ background: var(--bg); color: var(--text); font-family: 'Segoe UI', sans-serif;
         font-size: 14px; }}
  header {{ background: var(--card); border-bottom: 1px solid var(--border);
            padding: 14px 24px; display: flex; align-items: center; gap: 12px; }}
  header h1 {{ font-size: 18px; font-weight: 600; }}
  header .domain-pill {{ background: var(--accent); color: #fff;
    padding: 3px 10px; border-radius: 12px; font-size: 12px; }}
  header .refresh {{ margin-left: auto; font-size: 12px; color: var(--muted); }}
  .container {{ max-width: 1200px; margin: 24px auto; padding: 0 24px; }}
  .stats-row {{ display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px;
               margin-bottom: 24px; }}
  .stat-card {{ background: var(--card); border: 1px solid var(--border);
               border-radius: 10px; padding: 20px; }}
  .stat-card .label {{ color: var(--muted); font-size: 12px; text-transform: uppercase;
                       letter-spacing: 0.05em; margin-bottom: 8px; }}
  .stat-card .value {{ font-size: 28px; font-weight: 700; }}
  .stat-card .sub   {{ color: var(--muted); font-size: 12px; margin-top: 4px; }}
  .drift-banner {{ background: #3d3000; border: 1px solid var(--yellow);
                   border-radius: 8px; padding: 12px 16px; margin-bottom: 20px;
                   display: none; }}
  .drift-banner.visible {{ display: block; }}
  .chart-card {{ background: var(--card); border: 1px solid var(--border);
                border-radius: 10px; padding: 20px; margin-bottom: 24px; }}
  .chart-card h2 {{ font-size: 14px; font-weight: 600; margin-bottom: 16px;
                    color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; }}
  canvas {{ max-height: 200px; }}
  table {{ width: 100%; border-collapse: collapse; }}
  th {{ text-align: left; color: var(--muted); font-size: 11px;
        text-transform: uppercase; letter-spacing: 0.05em;
        border-bottom: 1px solid var(--border); padding: 8px 12px; }}
  td {{ padding: 10px 12px; border-bottom: 1px solid var(--border);
       vertical-align: top; max-width: 0; }}
  tr:last-child td {{ border-bottom: none; }}
  .risk-badge {{ display: inline-block; padding: 2px 8px; border-radius: 10px;
                font-size: 11px; font-weight: 600; white-space: nowrap; }}
  .risk-high   {{ background: rgba(239,68,68,0.2); color: var(--red); }}
  .risk-medium {{ background: rgba(234,179,8,0.2); color: var(--yellow); }}
  .risk-low    {{ background: rgba(34,197,94,0.2); color: var(--green); }}
  .truncate    {{ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }}
  @media(max-width:700px) {{ .stats-row {{ grid-template-columns: 1fr; }} }}
</style>
</head>
<body>
<header>
  <span style="font-size:20px">🛡️</span>
  <h1>llm-guard-kit</h1>
  <span class="domain-pill">{domain}</span>
  <span class="refresh" id="refresh-ts">Loading…</span>
</header>

<div class="container">
  <!-- Drift banner -->
  <div class="drift-banner" id="drift-banner">
    ⚠️ <strong>Risk Drift Detected:</strong> <span id="drift-msg"></span>
  </div>

  <!-- Stat cards -->
  <div class="stats-row">
    <div class="stat-card">
      <div class="label">Total Queries</div>
      <div class="value" id="n-tracked">—</div>
      <div class="sub" id="n-alerts-sub">— alerts</div>
    </div>
    <div class="stat-card">
      <div class="label">Alert Rate</div>
      <div class="value" id="alert-rate">—</div>
      <div class="sub">queries flagged for review</div>
    </div>
    <div class="stat-card">
      <div class="label">Avg Risk Score</div>
      <div class="value" id="avg-risk">—</div>
      <div class="sub" id="p95-sub">P95: —</div>
    </div>
  </div>

  <!-- Risk sparkline -->
  <div class="chart-card">
    <h2>Risk Score · Last 7 Days</h2>
    <canvas id="risk-chart"></canvas>
  </div>

  <!-- High-risk chains table -->
  <div class="chart-card">
    <h2>Recent High-Risk Chains</h2>
    <table>
      <thead>
        <tr>
          <th style="width:40%">Question</th>
          <th style="width:10%">Risk</th>
          <th style="width:15%">Failure Mode</th>
          <th style="width:12%">Steps</th>
          <th style="width:23%">Time</th>
        </tr>
      </thead>
      <tbody id="chains-tbody">
        <tr><td colspan="5" style="color:var(--muted);text-align:center">Loading…</td></tr>
      </tbody>
    </table>
  </div>
</div>

<script>
const DOMAIN = "{domain}";
let riskChart = null;

function riskBadge(r) {{
  if (r >= 0.65) return `<span class="risk-badge risk-high">${{r.toFixed(3)}}</span>`;
  if (r >= 0.40) return `<span class="risk-badge risk-medium">${{r.toFixed(3)}}</span>`;
  return `<span class="risk-badge risk-low">${{r.toFixed(3)}}</span>`;
}}

function fmtTime(ts) {{
  if (!ts) return "—";
  return new Date(ts * 1000).toLocaleString();
}}

async function loadStats() {{
  const r = await fetch(`/dashboard/api/stats?domain=${{DOMAIN}}`);
  const d = await r.json();
  document.getElementById("n-tracked").textContent = d.n_tracked ?? "—";
  document.getElementById("n-alerts-sub").textContent = `${{d.n_alerts ?? 0}} alerts`;
  document.getElementById("alert-rate").textContent =
    d.alert_rate != null ? (d.alert_rate * 100).toFixed(1) + "%" : "—";
  document.getElementById("avg-risk").textContent =
    d.avg_risk != null ? d.avg_risk.toFixed(3) : "—";
  document.getElementById("p95-sub").textContent =
    d.p95_risk != null ? `P95: ${{d.p95_risk.toFixed(3)}}` : "P95: —";

  const banner = document.getElementById("drift-banner");
  if (d.drift) {{
    banner.classList.add("visible");
    document.getElementById("drift-msg").textContent =
      `Mean risk shifted ${{d.drift.delta > 0 ? "+" : ""}}${{d.drift.delta.toFixed(3)}} `+
      `(current 7d: ${{d.drift.current_mean.toFixed(3)}} vs prev: ${{d.drift.previous_mean.toFixed(3)}})`;
  }} else {{
    banner.classList.remove("visible");
  }}
  document.getElementById("refresh-ts").textContent =
    "Refreshed " + new Date().toLocaleTimeString();
}}

async function loadTimeSeries() {{
  const r = await fetch(`/dashboard/api/timeseries?domain=${{DOMAIN}}&hours=168`);
  const pts = await r.json();
  const labels = pts.map(p => new Date(p.t * 1000).toLocaleDateString());
  const data   = pts.map(p => p.risk);

  if (riskChart) {{ riskChart.destroy(); }}
  const ctx = document.getElementById("risk-chart").getContext("2d");
  riskChart = new Chart(ctx, {{
    type: "line",
    data: {{
      labels,
      datasets: [{{
        label: "Risk Score",
        data,
        borderColor: "#6366f1",
        backgroundColor: "rgba(99,102,241,0.1)",
        tension: 0.3,
        pointRadius: pts.length < 50 ? 3 : 0,
        fill: true,
      }}]
    }},
    options: {{
      responsive: true,
      plugins: {{ legend: {{ display: false }} }},
      scales: {{
        y: {{ min: 0, max: 1, grid: {{ color: "#2d3148" }},
              ticks: {{ color: "#94a3b8" }} }},
        x: {{ grid: {{ display: false }}, ticks: {{ color: "#94a3b8", maxTicksLimit: 8 }} }}
      }}
    }}
  }});
}}

async function loadChains() {{
  const r = await fetch(`/dashboard/api/chains?domain=${{DOMAIN}}&limit=20`);
  const chains = await r.json();
  const tbody = document.getElementById("chains-tbody");
  if (!chains.length) {{
    tbody.innerHTML = `<tr><td colspan="5" style="color:var(--muted);text-align:center">
      No high-risk chains yet.</td></tr>`;
    return;
  }}
  tbody.innerHTML = chains.map(c => `
    <tr>
      <td class="truncate" style="max-width:300px" title="${{c.question}}">${{c.question}}</td>
      <td>${{riskBadge(c.risk_score)}}</td>
      <td style="color:var(--muted);font-size:12px">${{c.failure_mode || "—"}}</td>
      <td>${{c.n_steps ?? "—"}}</td>
      <td style="color:var(--muted);font-size:12px">${{fmtTime(c.timestamp)}}</td>
    </tr>`).join("");
}}

async function refresh() {{
  await Promise.all([loadStats(), loadTimeSeries(), loadChains()]);
}}

refresh();
setInterval(refresh, 30000);
</script>
</body>
</html>"""


# ── Route registration ─────────────────────────────────────────────────────────

def add_dashboard_routes(app: FastAPI, store: "ChainStore") -> None:
    """
    Register dashboard routes on an existing FastAPI app.

    Routes added:
        GET /dashboard                  — main dashboard HTML page
        GET /dashboard/api/stats        — summary stats JSON
        GET /dashboard/api/timeseries   — risk score time series JSON
        GET /dashboard/api/chains       — recent high-risk chains JSON
    """
    from .drift import DriftDetector
    detector = DriftDetector()

    @app.get("/dashboard", response_class=HTMLResponse, include_in_schema=False)
    async def dashboard_page(domain: str = "default"):
        return HTMLResponse(_HTML.replace("{domain}", domain))

    @app.get("/dashboard/api/stats", include_in_schema=False)
    async def dashboard_stats(domain: str = "default"):
        import numpy as np

        rows = store.get_chains(domain, limit=10_000)
        if not rows:
            return {"n_tracked": 0, "n_alerts": 0, "alert_rate": 0.0,
                    "avg_risk": 0.0, "p95_risk": 0.0, "drift": None}

        risks  = [r["risk_score"]      for r in rows]
        alerts = [r["alert_triggered"] for r in rows]
        n_alerts = sum(1 for a in alerts if a)

        drift_alert = detector.check(domain, store)
        drift_data = None
        if drift_alert:
            drift_data = {
                "delta":         drift_alert.delta,
                "current_mean":  drift_alert.current_mean,
                "previous_mean": drift_alert.previous_mean,
                "direction":     drift_alert.direction,
            }

        return {
            "n_tracked":  len(rows),
            "n_alerts":   n_alerts,
            "alert_rate": n_alerts / len(rows),
            "avg_risk":   round(float(np.mean(risks)), 4),
            "p95_risk":   round(float(np.percentile(risks, 95)), 4),
            "drift":      drift_data,
        }

    @app.get("/dashboard/api/timeseries", include_in_schema=False)
    async def dashboard_timeseries(domain: str = "default", hours: int = 168):
        since = time.time() - hours * 3600
        pts = store.get_recent_risk(domain, days=hours // 24 + 1)
        # Filter to requested window and downsample to ≤500 points
        pts = [(t, r) for t, r in pts if t >= since]
        step = max(1, len(pts) // 500)
        pts  = pts[::step]
        return [{"t": t, "risk": round(r, 4)} for t, r in pts]

    @app.get("/dashboard/api/chains", include_in_schema=False)
    async def dashboard_chains(domain: str = "default", limit: int = 20):
        rows = store.get_chains(domain, limit=500)
        # Sort by risk score descending, take top N
        rows.sort(key=lambda r: r.get("risk_score", 0), reverse=True)
        top = rows[:limit]
        return [
            {
                "question":     (r.get("question") or "")[:120],
                "risk_score":   round(r.get("risk_score", 0.0), 4),
                "failure_mode": r.get("failure_mode", ""),
                "n_steps":      r.get("n_steps", 0),
                "timestamp":    r.get("timestamp"),
                "alert":        bool(r.get("alert_triggered", 0)),
            }
            for r in top
        ]
