from __future__ import annotations

import argparse
import html
import json
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional


def _utc_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


def _read_jsonl(path: Path) -> Iterable[Dict[str, Any]]:
    for ln in path.read_text(encoding="utf-8").splitlines():
        s = (ln or "").strip()
        if not s:
            continue
        try:
            obj = json.loads(s)
            if isinstance(obj, dict):
                yield obj
        except Exception:
            continue


@dataclass(frozen=True)
class Summary:
    total_events: int
    requests: int
    responses: int
    tools: int
    interventions: int
    outcomes: int
    errors: int
    refusals: int
    tool_calls_total: int
    retries_total: int
    latency_ms_avg: float
    entropy_before_avg: float
    entropy_after_avg: float
    grounded_claims_total: int
    grounded_claims_supported: int

    def to_dict(self) -> Dict[str, Any]:
        return self.__dict__.copy()


def summarize_events(events: List[Dict[str, Any]]) -> Summary:
    def _getp(ev: Dict[str, Any], key: str) -> Any:
        p = ev.get("payload")
        if not isinstance(p, dict):
            return None
        return p.get(key)

    req = [e for e in events if e.get("type") == "request"]
    resp = [e for e in events if e.get("type") == "response"]
    tools = [e for e in events if e.get("type") == "tool"]
    inter = [e for e in events if e.get("type") == "intervention"]
    outc = [e for e in events if e.get("type") == "outcome"]
    errs = [e for e in events if e.get("type") == "error"]

    refusals = 0
    tool_calls_total = 0
    retries_total = 0
    lat: List[float] = []
    eb: List[float] = []
    ea: List[float] = []
    claim_total = 0
    claim_supported = 0
    for e in resp:
        try:
            refusals += 1 if bool(_getp(e, "is_refusal")) else 0
        except Exception:
            pass
        try:
            tool_calls_total += int(_getp(e, "tool_calls_count") or 0)
        except Exception:
            pass
        try:
            retries_total += int(_getp(e, "retry_count") or 0)
        except Exception:
            pass
        try:
            lat.append(float(_getp(e, "latency_ms") or 0.0))
        except Exception:
            pass
        try:
            v = _getp(e, "entropy_before")
            if v is not None:
                eb.append(float(v))
        except Exception:
            pass
        try:
            v = _getp(e, "entropy_after")
            if v is not None:
                ea.append(float(v))
        except Exception:
            pass
        gs = _getp(e, "groundedness_summary")
        if isinstance(gs, dict):
            try:
                claim_total += int(gs.get("total_claims", 0) or 0)
                claim_supported += int(gs.get("supported_claims", 0) or 0)
            except Exception:
                pass

    def _avg(xs: List[float]) -> float:
        return (sum(xs) / float(len(xs))) if xs else 0.0

    return Summary(
        total_events=len(events),
        requests=len(req),
        responses=len(resp),
        tools=len(tools),
        interventions=len(inter),
        outcomes=len(outc),
        errors=len(errs),
        refusals=int(refusals),
        tool_calls_total=int(tool_calls_total),
        retries_total=int(retries_total),
        latency_ms_avg=float(_avg(lat)),
        entropy_before_avg=float(_avg(eb)),
        entropy_after_avg=float(_avg(ea)),
        grounded_claims_total=int(claim_total),
        grounded_claims_supported=int(claim_supported),
    )


def _write_json(path: Path, obj: Any) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(obj, indent=2), encoding="utf-8")


def _write_html(path: Path, *, summary: Summary, run_meta: Dict[str, Any]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)

    def esc(s: Any) -> str:
        return html.escape(str(s), quote=False)

    supported_rate = float(summary.grounded_claims_supported) / float(
        max(1, summary.grounded_claims_total)
    )

    doc = f"""<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Entropic Runtime Report</title>
    <style>
      :root {{
        --bg: #0b0e12;
        --panel: rgba(255,255,255,0.03);
        --text: #e8eef8;
        --muted: #a8b3c7;
        --line: rgba(232,238,248,0.12);
      }}
      html, body {{ background: var(--bg); color: var(--text); margin: 0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif; }}
      .wrap {{ max-width: 1100px; margin: 0 auto; padding: 22px 16px 60px; }}
      h1 {{ margin: 0; font-size: 22px; }}
      .sub {{ color: var(--muted); font-size: 13px; margin-top: 6px; }}
      .grid {{ margin-top: 14px; display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; }}
      .card {{ background: var(--panel); border: 1px solid var(--line); border-radius: 14px; padding: 12px; }}
      .k {{ color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.35px; }}
      .v {{ margin-top: 8px; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 16px; }}
      code {{ font-family: ui-monospace, Menlo, Consolas, monospace; }}
      pre {{ margin-top: 12px; background: rgba(0,0,0,0.25); border: 1px solid var(--line); border-radius: 12px; padding: 10px; overflow: auto; font-size: 12px; }}
      @media (max-width: 980px) {{ .grid {{ grid-template-columns: 1fr; }} }}
    </style>
  </head>
  <body>
    <div class="wrap">
      <h1>Entropic Runtime Report</h1>
      <div class="sub">generated_utc: <code>{esc(_utc_iso())}</code></div>
      <div class="sub">run: <code>{esc(run_meta.get("run_id", ""))}</code> provider: <code>{esc(run_meta.get("provider", ""))}</code> model: <code>{esc(run_meta.get("model", ""))}</code></div>

      <div class="grid">
        <div class="card"><div class="k">Requests</div><div class="v">{summary.requests}</div></div>
        <div class="card"><div class="k">Responses</div><div class="v">{summary.responses}</div></div>
        <div class="card"><div class="k">Errors</div><div class="v">{summary.errors}</div></div>
        <div class="card"><div class="k">Interventions</div><div class="v">{summary.interventions}</div></div>
        <div class="card"><div class="k">Tool Calls</div><div class="v">{summary.tool_calls_total}</div></div>
        <div class="card"><div class="k">Refusals</div><div class="v">{summary.refusals}</div></div>
        <div class="card"><div class="k">Retries</div><div class="v">{summary.retries_total}</div></div>
        <div class="card"><div class="k">Latency Avg (ms)</div><div class="v">{summary.latency_ms_avg:.1f}</div></div>
        <div class="card"><div class="k">Entropy (before/after)</div><div class="v">{summary.entropy_before_avg:.3f} / {summary.entropy_after_avg:.3f}</div></div>
        <div class="card"><div class="k">Grounded Claims</div><div class="v">{summary.grounded_claims_supported}/{summary.grounded_claims_total}</div></div>
        <div class="card"><div class="k">Claim Supported Rate</div><div class="v">{supported_rate * 100:.1f}%</div></div>
        <div class="card"><div class="k">Total Events</div><div class="v">{summary.total_events}</div></div>
      </div>

      <h2 style="margin:16px 0 8px; font-size:14px; color: var(--muted);">Run Metadata</h2>
      <pre>{esc(json.dumps(run_meta, indent=2))}</pre>
    </div>
  </body>
</html>
"""
    path.write_text(doc, encoding="utf-8")


def generate_runtime_report(*, events_path: Path, outdir: Path, run_meta: Dict[str, Any]) -> Path:
    events = list(_read_jsonl(events_path)) if events_path.exists() else []
    summary = summarize_events(events)

    _write_json(
        outdir / "runtime_summary.json", {"run_meta": run_meta, "summary": summary.to_dict()}
    )
    _write_html(outdir / "runtime_report.html", summary=summary, run_meta=run_meta)
    return outdir / "runtime_report.html"


def main(argv: Optional[List[str]] = None) -> int:
    p = argparse.ArgumentParser(
        prog="entropic-runtime-report",
        description="Generate a runtime report from Entropic Core runtime events (JSONL).",
    )
    p.add_argument("--events", required=True, help="Path to events.jsonl")
    p.add_argument("--out", default="entropic_runtime_reports", help="Output directory")
    p.add_argument("--run-id", default="", help="Optional run id")
    p.add_argument("--provider", default="", help="Optional provider")
    p.add_argument("--model", default="", help="Optional model")
    args = p.parse_args(argv)

    events_path = Path(args.events)
    outdir = Path(args.out)
    run_meta = {"run_id": args.run_id, "provider": args.provider, "model": args.model}
    rp = generate_runtime_report(events_path=events_path, outdir=outdir, run_meta=run_meta)
    print(str(rp))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
