
import os
import sys
import json
import time
from datetime import datetime, timezone
from pathlib import Path
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
import uvicorn

SCRIPT_DIR = Path(__file__).resolve().parent
ROOT_DIR = SCRIPT_DIR.parent.parent.parent
if str(ROOT_DIR) not in sys.path:
    sys.path.insert(0, str(ROOT_DIR))
STATE_DIR = ROOT_DIR / "state"
HIPPOCAMPUS = ROOT_DIR / "Hippocampus"
TASK_BOARD = STATE_DIR / "task_board.json"
AGENT_REGISTRY = STATE_DIR / "agent_registry.json"
WORKER_PIDS = STATE_DIR / "worker_pids.json"

app = FastAPI(title="PULSE Dashboard")

# Mount the full API routers so ONE server serves everything
try:
    from pulse.api.pulse_api_brain import router as brain_router
    from pulse.api.pulse_api_agents import router as agents_router
    from pulse.api.pulse_api_system import router as system_router
    app.include_router(brain_router)
    app.include_router(agents_router)
    app.include_router(system_router)
except ImportError:
    pass  # Graceful if API modules unavailable


def _load_json(path):
    try:
        with open(path, "r", encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return {}


def _is_running(pid):
    if os.name == 'nt':
        import subprocess
        try:
            out = subprocess.check_output(["tasklist", "/FI", f"PID eq {pid}"],
                                          stderr=subprocess.DEVNULL, creationflags=0x08000000).decode()
            return str(pid) in out
        except Exception:
            return False
    try:
        os.kill(pid, 0)
        return True
    except OSError:
        return False


def _count_jsonl_items(filepath: Path) -> int:
    if not filepath.exists():
        return 0
    try:
        from pulse.core.brain_utils import read_jsonl_entries
        return len(read_jsonl_entries(filepath))
    except Exception:
        return 0


def _sqlite_count(table: str, where: str = "") -> int:
    """Count rows in a SQLite brain table."""
    try:
        from pulse.core.brain_db import get_conn
        conn = get_conn()
        q = f"SELECT COUNT(*) FROM [{table}]"
        if where:
            q += f" WHERE {where}"
        return conn.execute(q).fetchone()[0]
    except Exception:
        return 0


def _sqlite_samples(table: str, col: str = "content", where: str = "", n: int = 8) -> list:
    """Sample rows from a SQLite brain table."""
    try:
        from pulse.core.brain_db import get_conn
        conn = get_conn()
        q = f"SELECT {col} FROM [{table}]"
        if where:
            q += f" WHERE {where}"
        q += f" ORDER BY rowid DESC LIMIT {n}"
        rows = conn.execute(q).fetchall()
        return [str(r[0])[:120] for r in rows if r[0]]
    except Exception:
        return []


def _count_json_items(filepath: Path) -> int:
    if not filepath.exists():
        return 0
    try:
        data = json.loads(filepath.read_text(encoding="utf-8"))
        return len(data) if isinstance(data, list) else 0
    except Exception:
        return 0


def _sample_jsonl(filepath: Path, n: int = 8) -> list[str]:
    if not filepath.exists():
        return []
    try:
        from pulse.core.brain_utils import read_jsonl_entries
        entries = read_jsonl_entries(filepath)
        return [e.get("content", str(e))[:120] for e in entries[:n]]
    except Exception:
        return []


def _sample_json(filepath: Path, key: str, n: int = 8) -> list[str]:
    if not filepath.exists():
        return []
    try:
        data = json.loads(filepath.read_text(encoding="utf-8"))
        if not isinstance(data, list):
            return []
        return [str(item.get(key, item.get("want", item.get("dont_want", str(item)[:120]))))[:120]
                for item in data[:n]]
    except Exception:
        return []


@app.get("/api/status")
async def get_status():
    agents = _load_json(AGENT_REGISTRY)
    board = _load_json(TASK_BOARD)
    tasks_open, tasks_claimed, tasks_done = [], [], []
    for d in board.get("dispatches", []):
        for t in d.get("tasks", []):
            s = t.get("status", "")
            info = {"id": t.get("task_id"), "title": t.get("title")}
            if s == "open": tasks_open.append(info)
            elif s in ("active", "claimed"): tasks_claimed.append(info)
            elif s in ("done", "completed", "archived"): tasks_done.append(info)
    workers = []
    recs = _load_json(WORKER_PIDS)
    for r in (recs if isinstance(recs, list) else []):
        pid = r.get("pid", 0)
        workers.append({"id": r.get("id"), "pid": pid, "model": r.get("model"),
                         "tier": r.get("tier"), "status": "RUNNING" if _is_running(pid) else "STOPPED"})
    return {"timestamp": datetime.now(timezone.utc).isoformat(), "agents": agents,
            "tasks": {"open": tasks_open, "claimed": tasks_claimed, "done": tasks_done,
                      "counts": {"open": len(tasks_open), "claimed": len(tasks_claimed), "done": len(tasks_done)}},
            "workers": workers}


@app.get("/api/brain")
async def get_brain():
    from pulse.core.brain_utils import LESSONS_JSONL, FAILS_JSONL, PATTERNS_JSONL
    sections = [
        {"key": "lessons", "label": "Lessons", "color": "#4ade80",
         "count": _count_jsonl_items(LESSONS_JSONL),
         "samples": _sample_jsonl(LESSONS_JSONL)},
        {"key": "failures", "label": "Failures", "color": "#f87171",
         "count": _count_jsonl_items(FAILS_JSONL),
         "samples": _sample_jsonl(FAILS_JSONL)},
        {"key": "patterns", "label": "Patterns", "color": "#a78bfa",
         "count": _count_jsonl_items(PATTERNS_JSONL),
         "samples": _sample_jsonl(PATTERNS_JSONL)},
        {"key": "wants", "label": "Wants", "color": "#38bdf8",
         "count": _sqlite_count("intents", "type='want'"),
         "samples": _sqlite_samples("intents", "content", "type='want'")},
        {"key": "dont_wants", "label": "Don't Wants", "color": "#fb923c",
         "count": _sqlite_count("intents", "type='dont_want'"),
         "samples": _sqlite_samples("intents", "content", "type='dont_want'")},
        {"key": "intents", "label": "Strategic Intents", "color": "#e879f9",
         "count": _sqlite_count("intents", "type='strategic_intent'"),
         "samples": _sqlite_samples("intents", "content", "type='strategic_intent'")},
        {"key": "anti_patterns", "label": "Anti-Patterns", "color": "#fbbf24",
         "count": _sqlite_count("anti_patterns"),
         "samples": _sqlite_samples("anti_patterns", "description")},
    ]
    total = sum(s["count"] for s in sections)
    return {"total_rules": total, "sections": sections}


@app.get("/", response_class=HTMLResponse)
async def index():
    return HTMLResponse(content=DASHBOARD_HTML)


DASHBOARD_HTML = """<!DOCTYPE html><html><head><title>PULSE Dashboard</title><style>
body{font-family:'Segoe UI',sans-serif;background:#0f172a;color:#f8fafc;margin:20px}
h1{color:#38bdf8;border-bottom:2px solid #1e293b;padding-bottom:10px}
h2.section{color:#7dd3fc;margin:24px 0 12px;font-size:1.3rem}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:16px}
.card{background:#1e293b;border-radius:8px;padding:14px;box-shadow:0 4px 6px rgba(0,0,0,.1)}
.card h3{margin:0 0 8px;font-size:1.1rem;border-bottom:1px solid #334155;padding-bottom:6px}
.stat{font-size:2rem;font-weight:bold}.stat-sm{font-size:1.5rem;font-weight:bold}
.list-item{padding:4px 0;border-bottom:1px solid #334155;font-size:.85rem}
.list-item:last-child{border-bottom:none}
.tag{display:inline-block;padding:2px 6px;border-radius:4px;font-size:.7rem;font-weight:bold;margin-right:4px}
.tag-open{background:#0369a1;color:#e0f2fe}.tag-active{background:#a21caf;color:#fdf4ff}
.tag-done{background:#15803d;color:#f0fdf4}
.status-running{color:#4ade80}.status-stopped{color:#f87171}
#refresh-time{font-size:.8rem;color:#94a3b8;margin-bottom:16px}
.brain-count{font-size:.75rem;color:#94a3b8;margin-top:2px}
</style></head><body>
<h1>PULSE Neural OS Dashboard</h1>
<div id="refresh-time">Last updated: <span id="last-update">-</span></div>
<div class="grid">
  <div class="card"><h3>System Health</h3>
    <div>Total Brain Rules: <span class="stat" id="brain-total">0</span></div>
    <div style="margin-top:8px">Workers: <span id="worker-count">-</span></div></div>
  <div class="card"><h3>Task Board</h3>
    <div style="display:flex;justify-content:space-between">
      <div>Open: <span class="stat" id="count-open">0</span></div>
      <div>Active: <span class="stat" id="count-claimed">0</span></div>
      <div>Done: <span class="stat" id="count-done">0</span></div></div></div>
</div>
<h2 class="section">Knowledge Breakdown</h2>
<div class="grid" id="brain-grid"></div>
<div class="grid" style="margin-top:16px">
  <div class="card"><h3>Active Agents / Swarm</h3><div id="agents-list">Loading...</div></div>
  <div class="card"><h3>Workers</h3><div id="workers-list">Loading...</div></div>
</div>
<div class="card" style="margin-top:16px"><h3>Recent Tasks</h3><div id="tasks-list">Loading...</div></div>
<script>
async function refresh(){
  try{
    const [status,brain]=await Promise.all([fetch('/api/status').then(r=>r.json()),fetch('/api/brain').then(r=>r.json())]);
    document.getElementById('last-update').innerText=new Date(status.timestamp).toLocaleString();
    document.getElementById('brain-total').innerText=brain.total_rules.toLocaleString();
    document.getElementById('worker-count').innerText=status.workers.filter(w=>w.status==='RUNNING').length+' / '+status.workers.length;
    document.getElementById('count-open').innerText=status.tasks.counts.open;
    document.getElementById('count-claimed').innerText=status.tasks.counts.claimed;
    document.getElementById('count-done').innerText=status.tasks.counts.done;
    const bg=document.getElementById('brain-grid');bg.innerHTML='';
    brain.sections.forEach(s=>{
      const c=document.createElement('div');c.className='card';
      let items=s.samples.map(t=>'<div class="list-item">'+t.substring(0,100)+'</div>').join('');
      c.innerHTML='<h3 style="color:'+s.color+'">'+s.label+'</h3><div class="stat-sm" style="color:'+s.color+'">'+s.count.toLocaleString()+'</div><div class="brain-count">items in brain</div><div style="margin-top:8px;max-height:180px;overflow-y:auto">'+items+'</div>';
      bg.appendChild(c);
    });
    const al=document.getElementById('agents-list');al.innerHTML='';
    Object.values(status.agents).forEach(a=>{const d=document.createElement('div');d.className='list-item';d.innerHTML='<strong>'+a.agent_id+'</strong> - '+a.model+' <span class="tag tag-open">'+a.tier+'</span> '+a.status;al.appendChild(d)});
    if(!Object.keys(status.agents).length)al.innerHTML='No agents registered.';
    const wl=document.getElementById('workers-list');wl.innerHTML='';
    status.workers.forEach(w=>{const d=document.createElement('div');d.className='list-item';const sc=w.status==='RUNNING'?'status-running':'status-stopped';d.innerHTML='<strong>'+w.id+'</strong> (PID '+w.pid+') <span class="'+sc+'">'+w.status+'</span><br><small>'+w.model+' ['+w.tier+']</small>';wl.appendChild(d)});
    if(!status.workers.length)wl.innerHTML='No workers found.';
    const tl=document.getElementById('tasks-list');tl.innerHTML='';
    status.tasks.claimed.forEach(t=>{const d=document.createElement('div');d.className='list-item';d.innerHTML='<span class="tag tag-active">ACTIVE</span> '+t.title;tl.appendChild(d)});
    status.tasks.open.slice(0,5).forEach(t=>{const d=document.createElement('div');d.className='list-item';d.innerHTML='<span class="tag tag-open">OPEN</span> '+t.title;tl.appendChild(d)});
    status.tasks.done.slice(-5).reverse().forEach(t=>{const d=document.createElement('div');d.className='list-item';d.innerHTML='<span class="tag tag-done">DONE</span> '+t.title;tl.appendChild(d)});
  }catch(e){console.error('Refresh error:',e)}
}
setInterval(refresh,5000);refresh();
</script></body></html>"""

if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description="PULSE Web Dashboard")
    parser.add_argument("--host", default="127.0.0.1")
    parser.add_argument("--port", type=int, default=8000)
    args = parser.parse_args()
    uvicorn.run(app, host=args.host, port=args.port)
