#!/usr/bin/env python3
"""Minimal Ralph dashboard for localhost monitoring."""

from __future__ import annotations

import argparse
import json
import os
from datetime import datetime
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from typing import Any, Dict, List
from urllib.parse import urlparse


RALPH_ROOT = Path(__file__).resolve().parents[1]
STATE_DIR = RALPH_ROOT / "state"
RUNS_DIR = RALPH_ROOT / "runs"


def _read_json(path: Path, default: Any) -> Any:
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return default


def _read_text(path: Path) -> str:
    try:
        return path.read_text(encoding="utf-8")
    except Exception:
        return ""


def _tail_jsonl(path: Path, limit: int = 80) -> List[Dict[str, Any]]:
    if not path.exists():
        return []

    try:
        lines = path.read_text(encoding="utf-8").splitlines()
    except Exception:
        return []

    events: List[Dict[str, Any]] = []
    for line in lines[-limit:]:
        line = line.strip()
        if not line:
            continue
        try:
            events.append(json.loads(line))
        except Exception:
            continue
    events.reverse()
    return events


def _is_pid_running(pid: Any) -> bool:
    try:
        pid_value = int(pid)
    except Exception:
        return False

    if pid_value <= 0:
        return False

    try:
        os.kill(pid_value, 0)
    except PermissionError:
        return True
    except OSError:
        return False
    return True


def _iso_to_local(value: Any) -> str:
    if not value:
        return ""
    try:
        return datetime.fromisoformat(str(value).replace("Z", "+00:00")).strftime("%Y-%m-%d %H:%M:%S")
    except Exception:
        return str(value)


def _collect_recent_runs(limit: int = 8) -> List[Dict[str, Any]]:
    runs: List[Dict[str, Any]] = []
    if not RUNS_DIR.exists():
        return runs

    for run_dir in sorted([p for p in RUNS_DIR.iterdir() if p.is_dir()], key=lambda item: item.name, reverse=True)[:limit]:
        summary_path = run_dir / "SUMMARY.md"
        final_status_path = run_dir / "final_status.txt"
        summary_text = _read_text(summary_path).strip()
        final_status_text = _read_text(final_status_path).strip()
        changes_exists = (run_dir / "CHANGES.md").exists()
        implementer_patch = (run_dir / "implementer.patch").exists()
        final_patch = (run_dir / "final.patch").exists()
        runs.append(
            {
                "run_id": run_dir.name,
                "path": str(run_dir),
                "updated_at": datetime.fromtimestamp(run_dir.stat().st_mtime).strftime("%Y-%m-%d %H:%M:%S"),
                "summary_excerpt": "\n".join(summary_text.splitlines()[:10]),
                "final_status_excerpt": "\n".join(final_status_text.splitlines()[:8]),
                "changes_exists": changes_exists,
                "implementer_patch": implementer_patch,
                "final_patch": final_patch,
            }
        )
    return runs


def build_dashboard() -> Dict[str, Any]:
    current_run = _read_json(STATE_DIR / "current_run.json", {})
    background = _read_json(STATE_DIR / "last_background_run.json", {})
    background["alive"] = _is_pid_running(background.get("pid"))

    return {
        "generated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        "current_run": current_run,
        "background": background,
        "events": _tail_jsonl(STATE_DIR / "events.jsonl", limit=120),
        "recent_runs": _collect_recent_runs(),
        "provider_smoke": _read_json(STATE_DIR / "provider_smoke.json", {}),
    }


HTML_TEMPLATE = """<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Ralph Dashboard</title>
  <style>
    :root {
      --bg: #f3efe5;
      --ink: #1a1a1a;
      --muted: #5f5a52;
      --card: #fffaf2;
      --line: #d8cfbf;
      --ok: #2a7f62;
      --warn: #a96814;
      --bad: #ad2e24;
      --run: #144c7d;
      --shadow: rgba(0, 0, 0, 0.08);
    }
    * { box-sizing: border-box; }
    body {
      margin: 0;
      font-family: "Segoe UI", "Helvetica Neue", Arial, sans-serif;
      background: linear-gradient(180deg, #efe7d8 0%, var(--bg) 100%);
      color: var(--ink);
    }
    header {
      padding: 20px 24px;
      background: #17324d;
      color: #fff8eb;
      border-bottom: 4px solid #c98b2b;
    }
    header h1 { margin: 0 0 6px; font-size: 28px; }
    header p { margin: 0; color: #d7e4ef; }
    main { padding: 20px 24px 32px; }
    .grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
      gap: 16px;
      margin-bottom: 18px;
    }
    .card {
      background: var(--card);
      border: 1px solid var(--line);
      border-radius: 14px;
      padding: 16px;
      box-shadow: 0 8px 24px var(--shadow);
    }
    .card h2 {
      margin: 0 0 10px;
      font-size: 18px;
    }
    .muted { color: var(--muted); }
    .badge {
      display: inline-block;
      padding: 4px 9px;
      border-radius: 999px;
      font-size: 12px;
      font-weight: 700;
      text-transform: uppercase;
      letter-spacing: 0.04em;
      background: #ece4d6;
      color: var(--ink);
    }
    .status-completed, .status-success, .status-running { color: var(--ok); }
    .status-failed, .status-error { color: var(--bad); }
    .status-pending, .status-warning { color: var(--warn); }
    .agents { display: grid; gap: 10px; }
    .agent {
      border: 1px solid var(--line);
      border-radius: 10px;
      padding: 10px 12px;
      background: #fffdf8;
    }
    .agent strong { display: block; margin-bottom: 4px; }
    .timeline {
      list-style: none;
      margin: 0;
      padding: 0;
      display: grid;
      gap: 10px;
      max-height: 540px;
      overflow: auto;
    }
    .timeline li {
      border-left: 4px solid #c98b2b;
      padding: 8px 10px 8px 12px;
      background: #fffdf8;
      border-radius: 8px;
      border: 1px solid var(--line);
    }
    .run-list {
      display: grid;
      gap: 12px;
    }
    .run-item {
      border: 1px solid var(--line);
      border-radius: 12px;
      padding: 12px;
      background: #fffdf8;
    }
    pre {
      white-space: pre-wrap;
      word-break: break-word;
      background: #f6f1e8;
      border: 1px solid var(--line);
      border-radius: 10px;
      padding: 12px;
      margin: 8px 0 0;
      font-size: 12px;
    }
    .empty {
      padding: 16px;
      border: 1px dashed var(--line);
      border-radius: 10px;
      color: var(--muted);
      background: #faf6ef;
    }
  </style>
</head>
<body>
  <header>
    <h1>Ralph Dashboard</h1>
    <p>Simple local view of who is working, what stage the swarm is in, and what happened last.</p>
  </header>
  <main>
    <div class="grid">
      <section class="card">
        <h2>Overview</h2>
        <div id="overview" class="muted">Loading...</div>
      </section>
      <section class="card">
        <h2>Background Runner</h2>
        <div id="background" class="muted">Loading...</div>
      </section>
      <section class="card">
        <h2>Provider Smoke</h2>
        <div id="providers" class="muted">Loading...</div>
      </section>
    </div>

    <div class="grid">
      <section class="card">
        <h2>Current Run</h2>
        <div id="current-run" class="muted">Loading...</div>
      </section>
      <section class="card">
        <h2>Agent Status</h2>
        <div id="agents" class="agents"></div>
      </section>
    </div>

    <div class="grid">
      <section class="card">
        <h2>Timeline</h2>
        <ul id="timeline" class="timeline"></ul>
      </section>
      <section class="card">
        <h2>Recent Runs</h2>
        <div id="recent-runs" class="run-list"></div>
      </section>
    </div>
  </main>
  <script>
    function statusBadge(value) {
      const text = value || "unknown";
      const cls = "badge status-" + String(text).toLowerCase();
      return `<span class="${cls}">${text}</span>`;
    }

    function renderAgent(name, data) {
      if (!data) return "";
      const started = data.started_at ? new Date(data.started_at).toLocaleString() : "";
      const finished = data.finished_at ? new Date(data.finished_at).toLocaleString() : "";
      const output = data.output_file ? `<div class="muted">${data.output_file}</div>` : "";
      return `
        <div class="agent">
          <strong>${name}</strong>
          <div>${statusBadge(data.status)}</div>
          ${started ? `<div class="muted">Start: ${started}</div>` : ""}
          ${finished ? `<div class="muted">End: ${finished}</div>` : ""}
          ${output}
        </div>
      `;
    }

    function renderOverview(data) {
      const run = data.current_run || {};
      if (!run.run_id) {
        return `<div class="empty">No current run state has been written yet.</div>`;
      }
      return `
        <div><strong>Run ID:</strong> ${run.run_id}</div>
        <div><strong>Stage:</strong> ${statusBadge(run.stage)}</div>
        <div><strong>Status:</strong> ${statusBadge(run.status)}</div>
        <div><strong>Message:</strong> ${run.latest_message || "-"}</div>
        <div class="muted">Updated: ${data.generated_at}</div>
      `;
    }

    function renderBackground(data) {
      const bg = data.background || {};
      if (!bg.pid) {
        return `<div class="empty">No background launch metadata found.</div>`;
      }
      return `
        <div><strong>PID:</strong> ${bg.pid}</div>
        <div><strong>Alive:</strong> ${statusBadge(bg.alive ? "running" : "stopped")}</div>
        <div><strong>Run label:</strong> ${bg.run_label || "-"}</div>
        <div class="muted">${bg.stdout_log || ""}</div>
        <div class="muted">${bg.stderr_log || ""}</div>
      `;
    }

    function renderProviders(data) {
      const smoke = data.provider_smoke || {};
      const checks = smoke.checks || [];
      if (!checks.length) {
        return `<div class="empty">No provider smoke data found yet.</div>`;
      }
      return checks.map((item) => `
        <div class="agent">
          <strong>${item.provider || "provider"}</strong>
          <div>${statusBadge(item.status || "unknown")}</div>
          <div class="muted">${item.model || ""}</div>
        </div>
      `).join("");
    }

    function renderCurrentRun(data) {
      const run = data.current_run || {};
      if (!run.run_id) {
        return `<div class="empty">No active or recent run state.</div>`;
      }
      return `
        <div><strong>Run:</strong> ${run.run_id}</div>
        <div><strong>Task dir:</strong> ${run.task_directory || "-"}</div>
        <div><strong>Worktree:</strong> ${run.worktree || "-"}</div>
        <div><strong>Started:</strong> ${run.started_at ? new Date(run.started_at).toLocaleString() : "-"}</div>
        <div><strong>Finished:</strong> ${run.finished_at ? new Date(run.finished_at).toLocaleString() : "-"}</div>
        ${(run.errors || []).length ? `<pre>${(run.errors || []).join("\\n")}</pre>` : ""}
      `;
    }

    function renderAgents(data) {
      const run = data.current_run || {};
      const html = [];
      if (run.implementer) {
        html.push(renderAgent("Implementer: " + (run.implementer.name || "unknown"), run.implementer));
      }
      (run.reviewers || []).forEach((reviewer) => {
        html.push(renderAgent("Reviewer: " + (reviewer.name || "unknown"), reviewer));
      });
      if (run.codex_master) {
        html.push(renderAgent("Codex Master", run.codex_master));
      }
      if (run.fix_pass) {
        html.push(renderAgent("Fix Pass", run.fix_pass));
      }
      document.getElementById("agents").innerHTML = html.length ? html.join("") : `<div class="empty">No agent state yet.</div>`;
    }

    function renderTimeline(data) {
      const events = data.events || [];
      const target = document.getElementById("timeline");
      if (!events.length) {
        target.innerHTML = `<li class="empty">No events yet.</li>`;
        return;
      }
      target.innerHTML = events.map((event) => `
        <li>
          <div><strong>${event.actor || "system"}</strong> ${statusBadge(event.status || "unknown")}</div>
          <div>${event.message || "-"}</div>
          <div class="muted">${event.stage || "-"} · ${new Date(event.timestamp).toLocaleString()}</div>
        </li>
      `).join("");
    }

    function renderRecentRuns(data) {
      const runs = data.recent_runs || [];
      const target = document.getElementById("recent-runs");
      if (!runs.length) {
        target.innerHTML = `<div class="empty">No run folders found.</div>`;
        return;
      }
      target.innerHTML = runs.map((run) => `
        <div class="run-item">
          <div><strong>${run.run_id}</strong></div>
          <div class="muted">${run.updated_at}</div>
          <div>${run.changes_exists ? statusBadge("changes") : statusBadge("no changes")}</div>
          <div>${run.final_patch ? statusBadge("final patch") : statusBadge("no final patch")}</div>
          ${run.final_status_excerpt ? `<pre>${run.final_status_excerpt}</pre>` : ""}
          ${run.summary_excerpt ? `<pre>${run.summary_excerpt}</pre>` : ""}
        </div>
      `).join("");
    }

    async function refresh() {
      const response = await fetch("/api/dashboard", { cache: "no-store" });
      const data = await response.json();
      document.getElementById("overview").innerHTML = renderOverview(data);
      document.getElementById("background").innerHTML = renderBackground(data);
      document.getElementById("providers").innerHTML = renderProviders(data);
      document.getElementById("current-run").innerHTML = renderCurrentRun(data);
      renderAgents(data);
      renderTimeline(data);
      renderRecentRuns(data);
    }

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


class RalphHandler(BaseHTTPRequestHandler):
    def _send_json(self, payload: Dict[str, Any]) -> None:
        body = json.dumps(payload, indent=2).encode("utf-8")
        self.send_response(HTTPStatus.OK)
        self.send_header("Content-Type", "application/json; charset=utf-8")
        self.send_header("Cache-Control", "no-store")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def _send_html(self, payload: str) -> None:
        body = payload.encode("utf-8")
        self.send_response(HTTPStatus.OK)
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.send_header("Cache-Control", "no-store")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def do_GET(self) -> None:
        route = urlparse(self.path).path
        if route == "/api/dashboard":
            self._send_json(build_dashboard())
            return
        if route == "/" or route == "/index.html":
            self._send_html(HTML_TEMPLATE)
            return

        self.send_error(HTTPStatus.NOT_FOUND, "Not found")

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


def main() -> None:
    parser = argparse.ArgumentParser(description="Run the Ralph localhost dashboard.")
    parser.add_argument("--host", default="127.0.0.1", help="Bind host (default: 127.0.0.1)")
    parser.add_argument("--port", type=int, default=8765, help="Bind port (default: 8765)")
    args = parser.parse_args()

    server = ThreadingHTTPServer((args.host, args.port), RalphHandler)
    print(f"Ralph dashboard listening on http://{args.host}:{args.port}")
    server.serve_forever()


if __name__ == "__main__":
    main()
