import { logger } from "../logger.js";

/** Shape of the JSON body returned by `GET /health`. */
interface HealthStatus {
  /** Overall status of the process. Set to `"degraded"` on graceful shutdown. */
  status: "ok" | "degraded" | "error";
  /** Seconds elapsed since the process started (computed at request time). */
  uptime: number;
  /** ISO-8601 timestamp of when the process started. */
  startedAt: string;
  /** `true` once the {@link TaskLoop} has been started successfully. */
  taskLoop: boolean;
  /** `true` once the {@link ImprovementLoop} has been started successfully. */
  improvementLoop: boolean;
  /** ISO-8601 timestamp of the most recent Asana poll, if any. */
  lastPoll?: string;
}

/** In-memory snapshot of the current health state. */
let healthStatus: HealthStatus = {
  status: "ok",
  uptime: 0,
  startedAt: new Date().toISOString(),
  taskLoop: false,
  improvementLoop: false,
};

/**
 * Merges `updates` into the in-memory health snapshot.
 * Call this whenever a subsystem changes state (e.g. after starting a loop
 * or during graceful shutdown).
 *
 * @param updates - Partial fields to overwrite in the current health status.
 */
export function updateHealth(updates: Partial<HealthStatus>) {
  healthStatus = { ...healthStatus, ...updates };
}

/**
 * Security headers applied to every response from the health server.
 */
const SECURITY_HEADERS: Record<string, string> = {
  "X-Content-Type-Options": "nosniff",
  "X-Frame-Options": "DENY",
  "Cache-Control": "no-store",
};

/**
 * Starts a systemd watchdog heartbeat if WatchdogSec is configured.
 *
 * sd_notify is done by writing "WATCHDOG=1" to the NOTIFY_SOCKET.
 * Bun doesn't have native sd_notify, so we use a simple UDP/unix datagram.
 * Fallback: we just curl the health endpoint and if it responds, we're alive.
 */
let watchdogTimer: ReturnType<typeof setInterval> | null = null;

function startWatchdog(healthPort: number) {
  const watchdogUsec = process.env.WATCHDOG_USEC;
  if (!watchdogUsec) {
    logger.debug("health", "No WATCHDOG_USEC set, skipping systemd watchdog");
    return;
  }

  const intervalMs = Math.floor(parseInt(watchdogUsec) / 1000 / 2); // ping at half the deadline
  logger.info("health", `Systemd watchdog enabled, pinging every ${intervalMs}ms`);

  watchdogTimer = setInterval(async () => {
    try {
      // Self-check: hit our own health endpoint
      const res = await fetch(`http://127.0.0.1:${healthPort}/health`);
      if (!res.ok) {
        logger.error("health", `Watchdog self-check failed: ${res.status}`);
        return; // Don't notify systemd — let it kill us
      }

      const data = (await res.json()) as HealthStatus;
      if (data.status !== "ok") {
        logger.warn("health", `Watchdog: status is ${data.status}, not notifying systemd`);
        return;
      }

      // Notify systemd via systemd-notify command
      try {
        const proc = Bun.spawn({
          cmd: ["systemd-notify", "WATCHDOG=1"],
          stdout: "ignore",
          stderr: "ignore",
        });
        await proc.exited;
      } catch {
        // systemd-notify not available — not critical
      }
    } catch (e) {
      logger.error("health", `Watchdog ping failed: ${e}`);
      // Don't notify — let systemd restart us
    }
  }, intervalMs);
}

export function stopWatchdog() {
  if (watchdogTimer) {
    clearInterval(watchdogTimer);
    watchdogTimer = null;
  }
}

/**
 * Starts the HTTP health server on the given `port`.
 *
 * ### Endpoints
 *
 * | Method | Path      | Response |
 * |--------|-----------|----------|
 * | GET    | `/health` | `200 application/json` — {@link HealthStatus} with live `uptime` |
 * | GET    | `/`       | `200 text/plain` — simple alive message |
 * | *      | *         | `404 Not Found` |
 *
 * @param port - TCP port to listen on.
 * @returns The `Bun.Server` instance (rarely needed by callers).
 */
export function startHealthServer(port: number) {
  const startTime = Date.now();

  const server = Bun.serve({
    port,
    fetch(req) {
      const url = new URL(req.url);

      // Only allow safe read-only methods on this server.
      if (req.method !== "GET" && req.method !== "HEAD") {
        return new Response("Method Not Allowed", {
          status: 405,
          headers: { ...SECURITY_HEADERS, Allow: "GET, HEAD" },
        });
      }

      if (url.pathname === "/health") {
        return Response.json(
          {
            ...healthStatus,
            uptime: Math.floor((Date.now() - startTime) / 1000),
          },
          { headers: SECURITY_HEADERS }
        );
      }

      if (url.pathname === "/") {
        return new Response("Pi Worker is running. GET /health for status.", {
          headers: { ...SECURITY_HEADERS, "Content-Type": "text/plain" },
        });
      }

      return new Response("Not Found", { status: 404, headers: SECURITY_HEADERS });
    },
  });

  logger.info("health", `Health server listening on :${port}`);

  // Start systemd watchdog after health server is up
  startWatchdog(port);

  return server;
}
