/**
 * NodeBench MCP — Local Dashboard Server
 *
 * Serves a local web dashboard showing the full UI Dive flywheel cycle:
 * explored routes, components, interactions, screenshots, bugs,
 * code locations, fixes, changelogs, generated tests, code reviews.
 *
 * Inspired by Serena MCP's local dashboard. Zero external deps — uses
 * Node's built-in http module + reads from existing SQLite DB.
 */

import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
import type Database from "better-sqlite3";
import { getDashboardHtml } from "./html.js";

let _server: ReturnType<typeof createServer> | null = null;
let _port = 0;

/** Start the dashboard HTTP server. Returns the port it's listening on. */
export function startDashboardServer(db: Database.Database, preferredPort = 6274): Promise<number> {
  return new Promise((resolve, reject) => {
    if (_server) {
      resolve(_port);
      return;
    }

    _server = createServer((req, res) => handleRequest(db, req, res));

    let attempts = 0;
    const maxAttempts = 10;

    function tryListen(port: number) {
      _server!.listen(port, "127.0.0.1", () => {
        _port = port;
        resolve(_port);
      });
    }

    _server.on("error", (err: NodeJS.ErrnoException) => {
      if (err.code === "EADDRINUSE" && attempts < maxAttempts) {
        attempts++;
        tryListen(preferredPort + attempts);
      } else {
        reject(err);
      }
    });

    tryListen(preferredPort);
  });
}

/** Stop the dashboard server */
export function stopDashboardServer(): void {
  if (_server) {
    _server.close();
    _server = null;
    _port = 0;
  }
}

/** Get the current dashboard URL */
export function getDashboardUrl(): string | null {
  return _port ? `http://127.0.0.1:${_port}` : null;
}

// ── Request Router ──────────────────────────────────────────────────

function handleRequest(db: Database.Database, req: IncomingMessage, res: ServerResponse) {
  const url = new URL(req.url || "/", `http://127.0.0.1:${_port}`);
  const path = url.pathname;

  // CORS for local dev
  res.setHeader("Access-Control-Allow-Origin", "*");
  res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
  res.setHeader("Access-Control-Allow-Headers", "Content-Type");

  if (req.method === "OPTIONS") {
    res.writeHead(204);
    res.end();
    return;
  }

  try {
    if (path === "/" || path === "/index.html") {
      serveHtml(res);
    } else if (path === "/api/sessions") {
      apiSessions(db, res);
    } else if (path.startsWith("/api/screenshot/") && path.endsWith("/image")) {
      const ssId = decodeURIComponent(path.split("/api/screenshot/")[1].split("/image")[0]);
      apiScreenshotImage(db, ssId, res);
    } else if (path.startsWith("/api/session/")) {
      const sessionId = decodeURIComponent(path.split("/api/session/")[1].split("/")[0]);
      const sub = path.split(sessionId + "/")[1] || "overview";
      apiSessionDetail(db, sessionId, sub, res);
    } else if (path === "/api/latest") {
      apiLatestSession(db, res);
    } else if (path === "/api/agents/status") {
      apiAgentStatus(db, res);
    } else if (path === "/api/agents/activity") {
      apiAgentActivity(db, res);
    } else if (path === "/api/agents/mailbox") {
      apiAgentMailbox(db, res);
    } else {
      res.writeHead(404, { "Content-Type": "application/json" });
      res.end(JSON.stringify({ error: "Not found" }));
    }
  } catch (err: any) {
    res.writeHead(500, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ error: err.message }));
  }
}

// ── HTML ────────────────────────────────────────────────────────────

function serveHtml(res: ServerResponse) {
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
  res.end(getDashboardHtml());
}

// ── API: List sessions ──────────────────────────────────────────────

function apiSessions(db: Database.Database, res: ServerResponse) {
  const rows = db.prepare(`
    SELECT s.id, s.app_url, s.app_name, s.status, s.created_at, s.completed_at,
      (SELECT COUNT(*) FROM ui_dive_components WHERE session_id = s.id) as component_count,
      (SELECT COUNT(*) FROM ui_dive_bugs WHERE session_id = s.id) as bug_count,
      (SELECT COUNT(*) FROM ui_dive_bugs WHERE session_id = s.id AND status = 'resolved') as bugs_resolved,
      (SELECT COUNT(*) FROM ui_dive_interactions WHERE session_id = s.id) as interaction_count,
      (SELECT COUNT(*) FROM ui_dive_fix_verifications WHERE session_id = s.id) as fix_count,
      (SELECT COUNT(*) FROM ui_dive_code_reviews WHERE session_id = s.id) as review_count
    FROM ui_dive_sessions s
    ORDER BY s.created_at DESC
    LIMIT 50
  `).all();
  json(res, rows);
}

// ── API: Latest session redirect ────────────────────────────────────

function apiLatestSession(db: Database.Database, res: ServerResponse) {
  const row = db.prepare(`
    SELECT id FROM ui_dive_sessions ORDER BY created_at DESC LIMIT 1
  `).get() as { id: string } | undefined;

  if (!row) {
    json(res, { error: "No sessions found" }, 404);
    return;
  }

  // Return full overview for latest
  apiSessionDetail(db, row.id, "overview", res);
}

// ── API: Session detail ─────────────────────────────────────────────

function apiSessionDetail(db: Database.Database, sessionId: string, sub: string, res: ServerResponse) {
  switch (sub) {
    case "overview":
      return apiOverview(db, sessionId, res);
    case "components":
      return apiComponents(db, sessionId, res);
    case "bugs":
      return apiBugs(db, sessionId, res);
    case "interactions":
      return apiInteractions(db, sessionId, res);
    case "screenshots":
      return apiScreenshots(db, sessionId, res);
    case "code-locations":
      return apiCodeLocations(db, sessionId, res);
    case "fixes":
      return apiFixes(db, sessionId, res);
    case "changelogs":
      return apiChangelogs(db, sessionId, res);
    case "tests":
      return apiTests(db, sessionId, res);
    case "reviews":
      return apiReviews(db, sessionId, res);
    case "design-issues":
      return apiDesignIssues(db, sessionId, res);
    default:
      json(res, { error: `Unknown sub-route: ${sub}` }, 404);
  }
}

function apiOverview(db: Database.Database, sid: string, res: ServerResponse) {
  const session = db.prepare("SELECT * FROM ui_dive_sessions WHERE id = ?").get(sid);
  if (!session) { json(res, { error: "Session not found" }, 404); return; }

  const stats = {
    components: db.prepare("SELECT COUNT(*) as c FROM ui_dive_components WHERE session_id = ?").get(sid) as any,
    interactions: db.prepare("SELECT COUNT(*) as c FROM ui_dive_interactions WHERE session_id = ?").get(sid) as any,
    bugs: db.prepare("SELECT COUNT(*) as c FROM ui_dive_bugs WHERE session_id = ?").get(sid) as any,
    bugsOpen: db.prepare("SELECT COUNT(*) as c FROM ui_dive_bugs WHERE session_id = ? AND status = 'open'").get(sid) as any,
    bugsResolved: db.prepare("SELECT COUNT(*) as c FROM ui_dive_bugs WHERE session_id = ? AND status = 'resolved'").get(sid) as any,
    screenshots: db.prepare("SELECT COUNT(*) as c FROM ui_dive_screenshots WHERE session_id = ?").get(sid) as any,
    tests: db.prepare("SELECT COUNT(*) as c FROM ui_dive_interaction_tests WHERE session_id = ?").get(sid) as any,
    designIssues: db.prepare("SELECT COUNT(*) as c FROM ui_dive_design_issues WHERE session_id = ?").get(sid) as any,
    codeLocations: db.prepare("SELECT COUNT(*) as c FROM ui_dive_code_locations WHERE session_id = ?").get(sid) as any,
    fixes: db.prepare("SELECT COUNT(*) as c FROM ui_dive_fix_verifications WHERE session_id = ?").get(sid) as any,
    fixesVerified: db.prepare("SELECT COUNT(*) as c FROM ui_dive_fix_verifications WHERE session_id = ? AND verified = 1").get(sid) as any,
    changelogs: db.prepare("SELECT COUNT(*) as c FROM ui_dive_changelogs WHERE session_id = ?").get(sid) as any,
    generatedTests: db.prepare("SELECT COUNT(*) as c FROM ui_dive_generated_tests WHERE session_id = ?").get(sid) as any,
    codeReviews: db.prepare("SELECT COUNT(*) as c FROM ui_dive_code_reviews WHERE session_id = ?").get(sid) as any,
  };

  const latestReview = db.prepare(
    "SELECT score, summary, severity_counts, recommendations FROM ui_dive_code_reviews WHERE session_id = ? ORDER BY created_at DESC LIMIT 1"
  ).get(sid);

  json(res, {
    session,
    stats: Object.fromEntries(Object.entries(stats).map(([k, v]) => [k, (v as any)?.c ?? 0])),
    latestReview,
  });
}

function apiComponents(db: Database.Database, sid: string, res: ServerResponse) {
  json(res, db.prepare(
    "SELECT * FROM ui_dive_components WHERE session_id = ? ORDER BY created_at"
  ).all(sid));
}

function apiBugs(db: Database.Database, sid: string, res: ServerResponse) {
  json(res, db.prepare(`
    SELECT b.*, c.name as component_name
    FROM ui_dive_bugs b
    LEFT JOIN ui_dive_components c ON b.component_id = c.id
    WHERE b.session_id = ?
    ORDER BY
      CASE b.severity WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END,
      b.created_at
  `).all(sid));
}

function apiInteractions(db: Database.Database, sid: string, res: ServerResponse) {
  json(res, db.prepare(`
    SELECT i.*, c.name as component_name
    FROM ui_dive_interactions i
    LEFT JOIN ui_dive_components c ON i.component_id = c.id
    WHERE i.session_id = ?
    ORDER BY i.sequence_num
  `).all(sid));
}

function apiScreenshots(db: Database.Database, sid: string, res: ServerResponse) {
  json(res, db.prepare(
    "SELECT id, session_id, component_id, label, route, file_path, base64_thumbnail, width, height, metadata, created_at FROM ui_dive_screenshots WHERE session_id = ? ORDER BY created_at"
  ).all(sid));
}

/** Serve a screenshot as an actual PNG image */
function apiScreenshotImage(db: Database.Database, ssId: string, res: ServerResponse) {
  const row = db.prepare(
    "SELECT base64_thumbnail, file_path FROM ui_dive_screenshots WHERE id = ?"
  ).get(ssId) as { base64_thumbnail?: string; file_path?: string } | undefined;

  if (!row) {
    res.writeHead(404, { "Content-Type": "text/plain" });
    res.end("Screenshot not found");
    return;
  }

  // Prefer inline base64, fall back to file on disk
  if (row.base64_thumbnail) {
    const buf = Buffer.from(row.base64_thumbnail, "base64");
    res.writeHead(200, {
      "Content-Type": "image/png",
      "Content-Length": buf.length.toString(),
      "Cache-Control": "public, max-age=86400",
    });
    res.end(buf);
    return;
  }

  if (row.file_path) {
    try {
      const fs = require("node:fs");
      const buf = fs.readFileSync(row.file_path);
      res.writeHead(200, {
        "Content-Type": "image/png",
        "Content-Length": buf.length.toString(),
        "Cache-Control": "public, max-age=86400",
      });
      res.end(buf);
    } catch {
      res.writeHead(404, { "Content-Type": "text/plain" });
      res.end("Screenshot file not found on disk");
    }
    return;
  }

  res.writeHead(404, { "Content-Type": "text/plain" });
  res.end("No image data available");
}

function apiCodeLocations(db: Database.Database, sid: string, res: ServerResponse) {
  json(res, db.prepare(
    "SELECT * FROM ui_dive_code_locations WHERE session_id = ? ORDER BY created_at"
  ).all(sid));
}

function apiFixes(db: Database.Database, sid: string, res: ServerResponse) {
  json(res, db.prepare(`
    SELECT f.*, b.title as bug_title, b.severity as bug_severity, b.status as bug_status
    FROM ui_dive_fix_verifications f
    LEFT JOIN ui_dive_bugs b ON f.bug_id = b.id
    WHERE f.session_id = ?
    ORDER BY f.created_at
  `).all(sid));
}

function apiChangelogs(db: Database.Database, sid: string, res: ServerResponse) {
  json(res, db.prepare(
    "SELECT * FROM ui_dive_changelogs WHERE session_id = ? ORDER BY created_at DESC"
  ).all(sid));
}

function apiTests(db: Database.Database, sid: string, res: ServerResponse) {
  json(res, db.prepare(
    "SELECT * FROM ui_dive_generated_tests WHERE session_id = ? ORDER BY created_at"
  ).all(sid));
}

function apiReviews(db: Database.Database, sid: string, res: ServerResponse) {
  json(res, db.prepare(
    "SELECT * FROM ui_dive_code_reviews WHERE session_id = ? ORDER BY created_at DESC"
  ).all(sid));
}

function apiDesignIssues(db: Database.Database, sid: string, res: ServerResponse) {
  json(res, db.prepare(
    "SELECT * FROM ui_dive_design_issues WHERE session_id = ? ORDER BY created_at"
  ).all(sid));
}

// ── API: Agent Monitor ──────────────────────────────────────────────

function apiAgentStatus(db: Database.Database, res: ServerResponse) {
  // Only agents active in the last 2 hours (has recent calls OR a claimed task)
  const roles = db.prepare(`
    SELECT r.session_id, r.role, r.focus_area, r.created_at
    FROM agent_roles r
    WHERE EXISTS (
      SELECT 1 FROM tool_call_log t
      WHERE t.session_id = r.session_id AND t.created_at >= datetime('now', '-2 hours')
    ) OR EXISTS (
      SELECT 1 FROM agent_tasks a
      WHERE a.session_id = r.session_id AND a.status = 'claimed'
        AND a.claimed_at >= datetime('now', '-4 hours')
    )
    ORDER BY r.created_at DESC
    LIMIT 20
  `).all() as Array<{ session_id: string; role: string; focus_area: string | null; created_at: string }>;

  const agents = roles.map(r => {
    const task = db.prepare(`
      SELECT task_key, description, claimed_at
      FROM agent_tasks
      WHERE session_id = ? AND status = 'claimed'
      ORDER BY claimed_at DESC LIMIT 1
    `).get(r.session_id) as { task_key: string; description: string | null; claimed_at: string } | undefined;

    const callCount = db.prepare(`
      SELECT COUNT(*) as c FROM tool_call_log
      WHERE session_id = ? AND created_at >= datetime('now', '-30 minutes')
    `).get(r.session_id) as { c: number };

    const lastCall = db.prepare(`
      SELECT created_at FROM tool_call_log
      WHERE session_id = ? ORDER BY created_at DESC LIMIT 1
    `).get(r.session_id) as { created_at: string } | undefined;

    const budget = db.prepare(`
      SELECT tokens_used, tokens_limit FROM context_budget_log
      WHERE session_id = ? ORDER BY created_at DESC LIMIT 1
    `).get(r.session_id) as { tokens_used: number; tokens_limit: number } | undefined;

    const unread = db.prepare(`
      SELECT COUNT(*) as c FROM agent_mailbox
      WHERE (recipient_id = ? OR recipient_role = ?) AND read = 0
    `).get(r.session_id, r.role) as { c: number };

    return {
      sessionId: r.session_id,
      role: r.role,
      focusArea: r.focus_area,
      assignedAt: r.created_at,
      currentTask: task ? { key: task.task_key, description: task.description, claimedAt: task.claimed_at } : null,
      toolCallCount: callCount.c,
      lastCallAt: lastCall?.created_at ?? null,
      tokenBudget: budget
        ? { used: budget.tokens_used, limit: budget.tokens_limit, percent: Math.round((budget.tokens_used / budget.tokens_limit) * 100) }
        : { used: 0, limit: 200000, percent: 0 },
      unreadMessages: unread.c,
    };
  });

  // Summary stats
  const activeSessions = agents.map(a => a.sessionId);
  const totalCalls = activeSessions.length > 0
    ? (db.prepare(`SELECT COUNT(*) as c FROM tool_call_log WHERE session_id IN (${activeSessions.map(() => "?").join(",")})`)
        .get(...activeSessions) as { c: number }).c
    : 0;
  const claimedCount = agents.filter(a => a.currentTask).length;
  const completedTasks = db.prepare(`
    SELECT task_key, session_id, description, progress_note, released_at
    FROM agent_tasks
    WHERE status = 'released' AND released_at >= datetime('now', '-1 hour')
    ORDER BY released_at DESC LIMIT 20
  `).all() as Array<{ task_key: string; session_id: string; description: string | null; progress_note: string | null; released_at: string }>;

  json(res, {
    agents,
    completedTasks,
    summary: {
      activeAgents: agents.length,
      activeTasks: claimedCount,
      completedTasks: completedTasks.length,
      totalCalls,
    },
  });
}

function apiAgentActivity(db: Database.Database, res: ServerResponse) {
  // Only show calls from agents active in last 2 hours
  const calls = db.prepare(`
    SELECT t.tool_name, t.session_id, t.result_status, t.duration_ms, t.created_at
    FROM tool_call_log t
    WHERE EXISTS (
      SELECT 1 FROM agent_roles r
      WHERE r.session_id = t.session_id AND (
        EXISTS (SELECT 1 FROM tool_call_log t2 WHERE t2.session_id = r.session_id AND t2.created_at >= datetime('now', '-2 hours'))
        OR EXISTS (SELECT 1 FROM agent_tasks a WHERE a.session_id = r.session_id AND a.status = 'claimed')
      )
    )
    ORDER BY t.created_at DESC LIMIT 30
  `).all();

  json(res, { calls, totalCalls: calls.length });
}

function apiAgentMailbox(db: Database.Database, res: ServerResponse) {
  // Deduplicate by subject+sender — keep only the latest of each
  const messages = db.prepare(`
    SELECT id, sender_id, recipient_id, recipient_role, category, priority, subject, body, created_at
    FROM agent_mailbox
    WHERE read = 0 AND id IN (
      SELECT id FROM (
        SELECT id, ROW_NUMBER() OVER (PARTITION BY sender_id, subject ORDER BY created_at DESC) as rn
        FROM agent_mailbox WHERE read = 0
      ) WHERE rn = 1
    )
    ORDER BY
      CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'normal' THEN 2 ELSE 3 END,
      created_at DESC
    LIMIT 10
  `).all();

  json(res, { messages });
}

// ── Helpers ─────────────────────────────────────────────────────────

function json(res: ServerResponse, data: unknown, status = 200) {
  res.writeHead(status, { "Content-Type": "application/json" });
  res.end(JSON.stringify(data));
}
