"""
Web Dashboard — Lightweight agent management dashboard.

Serves an embedded single-page dashboard for monitoring NeuralClaw:
- Reasoning trace timeline (live via WebSocket)
- Memory health stats
- Swarm agent graph
- Skill usage
- API endpoints for programmatic access
"""

from __future__ import annotations

import asyncio
import json
import time
from typing import Any

try:
    from aiohttp import web
except ImportError:
    web = None  # type: ignore


# ---------------------------------------------------------------------------
# Dashboard HTML (embedded — no external dependencies)
# ---------------------------------------------------------------------------

DASHBOARD_HTML = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>NeuralClaw Dashboard</title>
<style>
  :root {
    --bg: #0d1117; --surface: #161b22; --border: #30363d;
    --text: #e6edf3; --muted: #8b949e; --accent: #58a6ff;
    --green: #3fb950; --red: #f85149; --orange: #d29922;
    --purple: #bc8cff; --font: 'Segoe UI', system-ui, sans-serif;
  }
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body { font-family: var(--font); background: var(--bg); color: var(--text); }
  .header {
    background: linear-gradient(135deg, #1a1e2e 0%, #0d1117 100%);
    padding: 20px 32px; border-bottom: 1px solid var(--border);
    display: flex; justify-content: space-between; align-items: center;
  }
  .header h1 { font-size: 1.4rem; }
  .header h1 span { color: var(--accent); }
  .status-dot { display: inline-block; width: 8px; height: 8px;
    border-radius: 50%; background: var(--green); margin-right: 8px;
    animation: pulse 2s infinite; }
  @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
  .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px;
    padding: 24px 32px; max-width: 1400px; }
  .card { background: var(--surface); border: 1px solid var(--border);
    border-radius: 12px; padding: 20px; }
  .card h2 { font-size: 0.85rem; text-transform: uppercase;
    color: var(--muted); margin-bottom: 12px; letter-spacing: 0.05em; }
  .stat-row { display: flex; justify-content: space-between;
    padding: 8px 0; border-bottom: 1px solid var(--border); }
  .stat-row:last-child { border-bottom: none; }
  .stat-label { color: var(--muted); }
  .stat-value { font-weight: 600; }
  .stat-value.good { color: var(--green); }
  .stat-value.warn { color: var(--orange); }
  .stat-value.bad { color: var(--red); }
  .trace-list { max-height: 400px; overflow-y: auto; font-size: 0.82rem; }
  .trace { padding: 8px 12px; border-left: 3px solid var(--border);
    margin-bottom: 4px; background: rgba(255,255,255,0.02); border-radius: 0 6px 6px 0; }
  .trace.perception { border-color: var(--accent); }
  .trace.memory { border-color: var(--purple); }
  .trace.reasoning { border-color: var(--green); }
  .trace.action { border-color: var(--orange); }
  .trace.swarm { border-color: var(--red); }
  .trace .ts { color: var(--muted); font-size: 0.75rem; }
  .agent-chip { display: inline-block; padding: 4px 10px; margin: 4px;
    border-radius: 16px; font-size: 0.8rem; border: 1px solid var(--border); }
  .agent-chip.online { border-color: var(--green); color: var(--green); }
  .agent-chip.offline { border-color: var(--muted); color: var(--muted); }
  .full-width { grid-column: 1 / -1; }
  #connection { font-size: 0.75rem; color: var(--muted); }
</style>
</head>
<body>
<div class="header">
  <h1><span class="status-dot"></span>Neural<span>Claw</span> Dashboard</h1>
  <span id="connection">Connecting...</span>
</div>
<div class="grid">
  <div class="card">
    <h2>System Status</h2>
    <div id="stats">Loading...</div>
  </div>
  <div class="card">
    <h2>Swarm Agents</h2>
    <div id="agents">No agents registered</div>
  </div>
  <div class="card full-width">
    <h2>Live Reasoning Traces</h2>
    <div id="traces" class="trace-list"></div>
  </div>
</div>
<script>
  const $ = id => document.getElementById(id);
  let ws;

  function connect() {
    const proto = location.protocol === 'https:' ? 'wss' : 'ws';
    ws = new WebSocket(`${proto}://${location.host}/ws/traces`);
    ws.onopen = () => { $('connection').textContent = 'Connected ✓'; };
    ws.onclose = () => {
      $('connection').textContent = 'Disconnected — reconnecting...';
      setTimeout(connect, 3000);
    };
    ws.onmessage = e => {
      const data = JSON.parse(e.data);
      if (data.type === 'trace') addTrace(data);
      else if (data.type === 'stats') updateStats(data);
      else if (data.type === 'agents') updateAgents(data);
    };
  }

  function addTrace(t) {
    const el = document.createElement('div');
    const cat = (t.category || 'action').toLowerCase();
    el.className = `trace ${cat}`;
    el.innerHTML = `<span class="ts">${new Date(t.timestamp*1000).toLocaleTimeString()}</span>
      &nbsp;[${(t.category||'').toUpperCase()}] ${t.message || ''}`;
    $('traces').prepend(el);
    if ($('traces').children.length > 200)
      $('traces').removeChild($('traces').lastChild);
  }

  function updateStats(s) {
    const d = s.data || {};
    const rate = d.success_rate || 0;
    const cls = rate > 0.8 ? 'good' : rate > 0.5 ? 'warn' : 'bad';
    $('stats').innerHTML = `
      <div class="stat-row"><span class="stat-label">Provider</span>
        <span class="stat-value">${d.provider||'—'}</span></div>
      <div class="stat-row"><span class="stat-label">Interactions</span>
        <span class="stat-value">${d.interactions||0}</span></div>
      <div class="stat-row"><span class="stat-label">Success Rate</span>
        <span class="stat-value ${cls}">${(rate*100).toFixed(0)}%</span></div>
      <div class="stat-row"><span class="stat-label">Skills Loaded</span>
        <span class="stat-value">${d.skills||0}</span></div>
      <div class="stat-row"><span class="stat-label">Channels</span>
        <span class="stat-value">${d.channels||'—'}</span></div>
      <div class="stat-row"><span class="stat-label">Uptime</span>
        <span class="stat-value">${d.uptime||'—'}</span></div>
    `;
  }

  function updateAgents(a) {
    const agents = a.data || [];
    if (!agents.length) { $('agents').textContent = 'No agents on mesh'; return; }
    $('agents').innerHTML = agents.map(ag =>
      `<span class="agent-chip ${ag.status?.toLowerCase()}">${ag.name} (${ag.capabilities?.join(', ')})</span>`
    ).join('');
  }

  connect();
  // Fetch initial stats
  fetch('/api/stats').then(r=>r.json()).then(d=>updateStats({data:d})).catch(()=>{});
  fetch('/api/agents').then(r=>r.json()).then(d=>updateAgents({data:d})).catch(()=>{});
</script>
</body>
</html>"""


# ---------------------------------------------------------------------------
# Dashboard Server
# ---------------------------------------------------------------------------

class Dashboard:
    """
    Lightweight aiohttp-based dashboard for NeuralClaw.

    Routes:
      GET  /           — Dashboard UI
      GET  /api/stats  — System statistics (JSON)
      GET  /api/traces — Recent reasoning traces (JSON)
      GET  /api/agents — Active swarm agents (JSON)
      WS   /ws/traces  — Live trace streaming
    """

    def __init__(
        self,
        host: str = "0.0.0.0",
        port: int = 8080,
    ) -> None:
        self._host = host
        self._port = port
        self._app: Any = None
        self._runner: Any = None
        self._ws_clients: list[Any] = []
        self._traces: list[dict[str, Any]] = []
        self._stats_provider: Any = None
        self._agents_provider: Any = None
        self._start_time = time.time()

    def set_stats_provider(self, provider: Any) -> None:
        """Set a callable that returns stats dict."""
        self._stats_provider = provider

    def set_agents_provider(self, provider: Any) -> None:
        """Set a callable that returns list of agent dicts."""
        self._agents_provider = provider

    def push_trace(self, category: str, message: str, data: dict[str, Any] | None = None) -> None:
        """Push a trace event to the dashboard (stored + broadcast to WS clients)."""
        trace = {
            "type": "trace",
            "category": category,
            "message": message,
            "timestamp": time.time(),
            "data": data or {},
        }
        self._traces.append(trace)
        if len(self._traces) > 500:
            self._traces = self._traces[-500:]

        # Broadcast to WebSocket clients
        asyncio.ensure_future(self._broadcast(trace))

    async def start(self) -> None:
        """Start the dashboard server."""
        if web is None:
            print("[Dashboard] aiohttp not installed — dashboard unavailable")
            return

        self._app = web.Application()
        self._app.router.add_get("/", self._handle_index)
        self._app.router.add_get("/api/stats", self._handle_stats)
        self._app.router.add_get("/api/traces", self._handle_traces)
        self._app.router.add_get("/api/agents", self._handle_agents)
        self._app.router.add_get("/ws/traces", self._handle_ws)

        self._runner = web.AppRunner(self._app)
        await self._runner.setup()
        site = web.TCPSite(self._runner, self._host, self._port)
        await site.start()
        print(f"[Dashboard] ✓ Running at http://localhost:{self._port}")

    async def stop(self) -> None:
        """Stop the dashboard server."""
        for ws_client in self._ws_clients:
            await ws_client.close()
        if self._runner:
            await self._runner.cleanup()

    # -- Route handlers --

    async def _handle_index(self, request: Any) -> Any:
        return web.Response(text=DASHBOARD_HTML, content_type="text/html")

    async def _handle_stats(self, request: Any) -> Any:
        stats = {}
        if self._stats_provider:
            stats = self._stats_provider()
        stats["uptime"] = self._format_uptime()
        return web.json_response(stats)

    async def _handle_traces(self, request: Any) -> Any:
        limit = int(request.query.get("limit", "50"))
        return web.json_response(self._traces[-limit:])

    async def _handle_agents(self, request: Any) -> Any:
        agents = []
        if self._agents_provider:
            agents = self._agents_provider()
        return web.json_response(agents)

    async def _handle_ws(self, request: Any) -> Any:
        ws_response = web.WebSocketResponse()
        await ws_response.prepare(request)
        self._ws_clients.append(ws_response)

        try:
            async for msg in ws_response:
                pass  # Client just listens
        finally:
            self._ws_clients.remove(ws_response)
        return ws_response

    async def _broadcast(self, data: dict[str, Any]) -> None:
        """Broadcast a message to all WebSocket clients."""
        if not self._ws_clients:
            return
        payload = json.dumps(data)
        for ws_client in list(self._ws_clients):
            try:
                await ws_client.send_str(payload)
            except Exception:
                self._ws_clients.remove(ws_client)

    def _format_uptime(self) -> str:
        elapsed = int(time.time() - self._start_time)
        hours, remainder = divmod(elapsed, 3600)
        minutes, seconds = divmod(remainder, 60)
        if hours > 0:
            return f"{hours}h {minutes}m"
        return f"{minutes}m {seconds}s"
