import json
import logging
import os
import secrets
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from threading import Event, Thread
from typing import Any
from urllib.parse import parse_qs, urlparse

from .remote import parse_agent_registration, parse_remote_event
from .reports import _row
from .watcher import Watcher

LOGGER = logging.getLogger(__name__)


WATCHER_HTML = """<!doctype html>
<html lang="de">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Worker Watcher · Agent Solutions</title>
  <style>
    :root { color-scheme: dark; --bg:#081416; --panel:#0d2022; --line:#244044; --text:#f4e4d2; --muted:#9bb0ae; --green:#54d98a; --yellow:#f4c95d; --red:#ff7777; --blue:#8dc8ff; }
    * { box-sizing:border-box; } body { margin:0; background:var(--bg); color:var(--text); font:15px/1.45 system-ui,-apple-system,sans-serif; }
    header { padding:24px 5vw 20px; border-bottom:1px solid var(--line); display:flex; justify-content:space-between; gap:20px; align-items:center; }
    h1 { margin:0; font-size:28px; } h2 { margin:0 0 16px; font-size:19px; } .sub { color:var(--muted); margin-top:4px; }
    main { width:min(1280px,90vw); margin:28px auto 50px; } .grid { display:grid; grid-template-columns:repeat(5,1fr); gap:12px; margin-bottom:24px; }
    .card,.section { background:var(--panel); border:1px solid var(--line); border-radius:10px; } .card { padding:16px; }
    .label { color:var(--muted); font-size:12px; text-transform:uppercase; letter-spacing:.08em; } .value { font-size:28px; margin-top:4px; }
    .section { padding:20px; margin-bottom:20px; overflow:auto; } table { border-collapse:collapse; width:100%; min-width:700px; } th,td { text-align:left; padding:10px 8px; border-bottom:1px solid var(--line); vertical-align:top; } th { color:var(--muted); font-size:12px; text-transform:uppercase; letter-spacing:.06em; }
    .pill { display:inline-block; padding:3px 9px; border-radius:999px; border:1px solid var(--line); font-size:12px; } .healthy,.online { color:var(--green); border-color:#246b48; } .failed,.stale { color:var(--red); border-color:#713b3b; } .degraded,.unknown { color:var(--yellow); border-color:#725d2c; } .fresh { color:var(--green); } .muted { color:var(--muted); }
    button { color:var(--blue); background:transparent; border:1px solid var(--line); border-radius:6px; padding:8px 12px; cursor:pointer; } button:hover { border-color:var(--blue); }
    .empty { color:var(--muted); padding:10px 0; } @media (max-width:850px) { .grid { grid-template-columns:repeat(2,1fr); } header { align-items:flex-start; flex-direction:column; } } @media (max-width:500px) { .grid { grid-template-columns:1fr; } }
  </style>
</head>
<body>
  <header><div><h1>Worker Watcher</h1><div class="sub">Zentrale read-only Überwachung von KI-Workern und Automationen</div></div><button id="refresh">Aktualisieren</button></header>
  <main>
    <div class="grid" id="summary"></div>
    <section class="section"><h2>Zentrale Infrastruktur</h2><div id="jobs-core" class="empty">Lade Daten …</div></section>
    <section class="section"><h2>Projekt-Worker</h2><div id="jobs-projects" class="empty">Lade Daten …</div></section>
    <section class="section"><h2>Automationen und Hintergrund-Worker</h2><div id="jobs-automation" class="empty">Lade Daten …</div></section>
    <section class="section"><h2>Weitere überwachte Jobs</h2><div id="jobs-other" class="empty">Lade Daten …</div></section>
    <section class="section"><h2>Ausführungsorte und Remote-Agenten</h2><div id="agents" class="empty">Lade Daten …</div></section>
    <section class="section"><h2>Letzte Ereignisse</h2><div id="events" class="empty">Lade Daten …</div></section>
  </main>
  <script>
    const esc = value => String(value ?? '—').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[c]));
    const pill = value => `<span class="pill ${esc(value)}">${esc(value)}</span>`;
    const get = path => fetch(path, {cache:'no-store'}).then(r => { if (!r.ok) throw new Error(r.status); return r.json(); });
    function table(target, headers, rows) {
      if (!rows.length) { document.getElementById(target).innerHTML = '<div class="empty">Noch keine Daten vorhanden.</div>'; return; }
      document.getElementById(target).innerHTML = `<table><thead><tr>${headers.map(h => `<th>${esc(h)}</th>`).join('')}</tr></thead><tbody>${rows.join('')}</tbody></table>`;
    }
    const projectNames = {lead:'Lead Engine',social:'Social Media',telefon:'Telefon Agent',vento:'Vento',youtube:'YouTube Research',content:'Content Extraction',solar:'Solar',mirofish:'MiroFish'};
    const projectName = job => projectNames[job.job_key.split('.')[1]] || 'Projekt';
    const lastRun = job => job.last_finished_at || job.last_started_at || job.last_activity_at || job.last_observed_at || '—';
    const jobRow = (job, withProject) => `<tr>${withProject ? `<td>${esc(projectName(job))}</td>` : ''}<td>${esc(job.name)}<br><span class="muted">${esc(job.job_key)}</span></td><td>${esc(job.worker_key)}</td><td>${esc(job.location_name || job.host)}</td><td>${esc(lastRun(job))}</td><td>${pill(job.health_status)}</td><td>${pill(job.observation_status)}</td><td>${esc(job.status_reason)}</td></tr>`;
    async function refresh() {
      try {
        const [status, jobs, agents, events] = await Promise.all([get('/api/v1/status'), get('/api/v1/jobs'), get('/api/v1/agents'), get('/api/v1/events?limit=25')]);
        const s = status;
        document.getElementById('summary').innerHTML = [['Watcher',s.watcher_status],['Jobs',s.jobs_total],['Healthy',s.jobs_healthy],['Fehler',s.jobs_failed],['Agents',agents.items.length]].map(x => `<div class="card"><div class="label">${esc(x[0])}</div><div class="value">${esc(x[1])}</div></div>`).join('');
        const groups = {core:[], projects:[], automation:[], other:[]};
        jobs.items.forEach(j => { const key = j.job_key; if (key.startsWith('project.')) groups.projects.push(j); else if (key.startsWith('automation.')) groups.automation.push(j); else if (key.startsWith('agent.') || key.startsWith('dashboard.') || key.startsWith('portal.') || key.startsWith('admin.') || key.startsWith('commander.') || key.startsWith('editor.') || key.startsWith('watcher.') || key.startsWith('process.')) groups.core.push(j); else groups.other.push(j); });
        const headers = ['Job / Instanz','Worker','Ort','Letzter Lauf / letzte Prüfung','Status','Beobachtung','Grund'];
        table('jobs-core',headers,groups.core.map(j => jobRow(j,false)));
        table('jobs-projects',['Projekt',...headers],groups.projects.map(j => jobRow(j,true)));
        table('jobs-automation',headers,groups.automation.map(j => jobRow(j,false)));
        table('jobs-other',headers,groups.other.map(j => jobRow(j,false)));
        table('agents',['Agent','Umgebung','Host','Runtime','Status','Letzter Heartbeat'], agents.items.map(a => `<tr><td>${esc(a.agent_name)}<br><span class="muted">${esc(a.agent_key)}</span></td><td>${esc(a.environment)}</td><td>${esc(a.host)}</td><td>${esc(a.runtime)}</td><td>${pill(a.status)}</td><td>${esc(a.last_heartbeat_at)}</td></tr>`));
        table('events',['Zeit','Typ','Schwere','Komponente','Grund'], events.items.map(e => `<tr><td>${esc(e.occurred_at)}</td><td>${esc(e.event_type)}</td><td>${pill(e.new_severity || e.severity)}</td><td>${esc(e.component || e.instance_id)}</td><td>${esc(e.reason || e.message)}</td></tr>`));
      } catch (error) { document.getElementById('summary').innerHTML = `<div class="card"><div class="label">Verbindung</div><div class="value">Fehler</div><div class="muted">${esc(error)}</div></div>`; }
    }
    document.getElementById('refresh').addEventListener('click', refresh); refresh(); setInterval(refresh, 15000);
  </script>
</body>
</html>"""


def create_handler(watcher: Watcher) -> type[BaseHTTPRequestHandler]:
    class Handler(BaseHTTPRequestHandler):
        def log_message(self, format: str, *args: Any) -> None:
            return

        def send_json(self, status: int, payload: Any) -> None:
            body = json.dumps(payload, ensure_ascii=False, default=str).encode("utf-8")
            self.send_response(status)
            self.send_header("Content-Type", "application/json; charset=utf-8")
            self.send_header("Content-Length", str(len(body)))
            self.end_headers()
            self.wfile.write(body)

        def send_html(self, status: int, body: str) -> None:
            encoded = body.encode("utf-8")
            self.send_response(status)
            self.send_header("Content-Type", "text/html; charset=utf-8")
            self.send_header("Content-Length", str(len(encoded)))
            self.end_headers()
            self.wfile.write(encoded)

        def read_json(self) -> dict[str, Any] | None:
            try:
                length = int(self.headers.get("Content-Length", "0"))
                if length <= 0 or length > watcher.config.watcher.max_remote_event_bytes:
                    return None
                payload = json.loads(self.rfile.read(length))
                return payload if isinstance(payload, dict) else None
            except (ValueError, json.JSONDecodeError):
                return None

        def authorized_agent_request(self) -> bool:
            expected = os.environ.get(watcher.config.watcher.agent_auth_token_env)
            if not expected:
                return True
            provided = self.headers.get("Authorization", "")
            return secrets.compare_digest(provided, f"Bearer {expected}")

        def do_GET(self) -> None:
            parsed = urlparse(self.path)
            path = parsed.path.rstrip("/") or "/"
            if path == "/":
                self.send_html(200, WATCHER_HTML)
            elif path == "/health/live":
                self.send_json(200, {"status": "live"})
            elif path == "/health/ready":
                try:
                    watcher.connection.execute("SELECT 1").fetchone()
                    self.send_json(200, {"status": "ready", "schema_version": watcher.schema_version})
                except Exception as exc:  # noqa: BLE001 - readiness must never expose internals
                    self.send_json(503, {"status": "not_ready", "error": type(exc).__name__})
            elif path == "/health" or path == "/api/v1/status":
                self.send_json(200, watcher.health())
            elif path == "/api/v1/jobs":
                query = parse_qs(parsed.query)
                rows = watcher.repository.status_rows()
                status = query.get("status", [None])[0]
                if status:
                    rows = [row for row in rows if row["health_status"] == status or row["operational_state"] == status]
                self.send_json(200, {"items": [_row(row) for row in rows]})
            elif path == "/api/v1/workers":
                self.send_json(200, {"items": [_row(row) for row in watcher.repository.worker_rows()]})
            elif path == "/api/v1/agents":
                self.send_json(200, {"items": [_row(row) for row in watcher.repository.agent_rows()]})
            elif path == "/api/v1/remote-events":
                limit = int(parse_qs(parsed.query).get("limit", [100])[0])
                self.send_json(200, {"items": [_row(row) for row in watcher.repository.remote_event_rows(limit)]})
            elif path.startswith("/api/v1/jobs/"):
                try:
                    instance_id = int(path.rsplit("/", 1)[1])
                except ValueError:
                    self.send_json(404, {"error": "instance not found"})
                    return
                row = watcher.connection.execute("SELECT * FROM job_status_current WHERE instance_id=?", (instance_id,)).fetchone()
                self.send_json(200, _row(row) if row else {"error": "instance not found"}) if row else self.send_json(404, {"error": "instance not found"})
            elif path == "/api/v1/events":
                limit = int(parse_qs(parsed.query).get("limit", [50])[0])
                self.send_json(200, {"items": [_row(row) for row in watcher.repository.events(limit)]})
            else:
                self.send_json(404, {"error": "not found"})

        def do_POST(self) -> None:
            path = urlparse(self.path).path.rstrip("/") or "/"
            if path not in {"/api/v1/agents/register", "/api/v1/agents/events"}:
                self.send_json(404, {"error": "not found"})
                return
            if not self.authorized_agent_request():
                self.send_json(401, {"error": "agent authentication required"})
                return
            if not watcher.config.watcher.remote_ingest_enabled:
                self.send_json(404, {"error": "remote ingest disabled"})
                return
            payload = self.read_json()
            if payload is None:
                self.send_json(400, {"error": "JSON object required and limited to 512 KiB"})
                return
            try:
                if path.endswith("/register"):
                    registration = parse_agent_registration(payload)
                    agent_id = watcher.register_remote_agent(registration)
                    self.send_json(201, {"accepted": True, "agent_id": agent_id, "agent_key": registration.agent_key})
                else:
                    event = parse_remote_event(payload)
                    self.send_json(200, watcher.ingest_remote_event(event))
            except (KeyError, TypeError, ValueError) as exc:
                self.send_json(422, {"error": str(exc)})

    return Handler


def serve(watcher: Watcher, host: str, port: int) -> None:
    stop = Event()

    def scan_loop() -> None:
        while not stop.is_set():
            try:
                watcher.run_once()
            except Exception:
                LOGGER.exception("watcher cycle failed")
            stop.wait(watcher.config.watcher.cycle_interval_seconds)

    scan_thread = Thread(target=scan_loop, name="watcher-cycle", daemon=True)
    scan_thread.start()
    server = ThreadingHTTPServer((host, port), create_handler(watcher))
    try:
        server.serve_forever()
    finally:
        stop.set()
        scan_thread.join(timeout=2)
        server.server_close()
