import type { Hono } from "hono";
import { z } from "zod";
import type { CliLauncher } from "../cli-launcher.js";
import type { WsBridge } from "../ws-bridge.js";
import type { TerminalManager } from "../terminal-manager.js";
import { parseBody } from "../validation.js";
import { getUsageLimits } from "../usage-limits.js";
import {
  getUpdateState,
  checkForUpdate,
  isUpdateAvailable,
  setUpdateInProgress,
} from "../update-checker.js";
import { refreshServiceDefinition } from "../service.js";
import { createLogger } from "../logger.js";

const log = createLogger("system-routes");

// ─── Zod Schemas ──────────────────────────────────────────────────

const terminalSpawnSchema = z.object({
  cwd: z.string().min(1, "cwd is required"),
  cols: z.number().int().min(1).max(1000).optional(),
  rows: z.number().int().min(1).max(500).optional(),
  containerId: z.string().max(200).optional(),
});

const terminalKillSchema = z.object({
  terminalId: z.string().min(1, "terminalId is required"),
});

const sessionMessageSchema = z.object({
  content: z.string().min(1, "content is required").max(100_000),
});

export function registerSystemRoutes(
  api: Hono,
  deps: {
    launcher: CliLauncher;
    wsBridge: WsBridge;
    terminalManager: TerminalManager;
    updateCheckStaleMs: number;
  },
): void {
  api.get("/usage-limits", async (c) => {
    const limits = await getUsageLimits();
    return c.json(limits);
  });

  api.get("/sessions/:id/usage-limits", async (c) => {
    const sessionId = c.req.param("id");
    const session = deps.wsBridge.getSession(sessionId);
    const empty = { five_hour: null, seven_day: null, extra_usage: null };

    if (session?.backendType === "codex") {
      const rl = deps.wsBridge.getCodexRateLimits(sessionId);
      if (!rl) return c.json(empty);
      const toEpochMs = (value: number): number => (
        value > 0 && value < 1_000_000_000_000 ? value * 1000 : value
      );
      const mapLimit = (l: { usedPercent: number; windowDurationMins: number; resetsAt: number } | null) => {
        if (!l) return null;
        const resetsAtMs = toEpochMs(l.resetsAt);
        return {
          utilization: l.usedPercent,
          resets_at: resetsAtMs ? new Date(resetsAtMs).toISOString() : null,
        };
      };
      return c.json({
        five_hour: mapLimit(rl.primary),
        seven_day: mapLimit(rl.secondary),
        extra_usage: null,
      });
    }

    const limits = await getUsageLimits();
    return c.json(limits);
  });

  api.get("/update-check", async (c) => {
    const initialState = getUpdateState();
    const needsRefresh =
      initialState.lastChecked === 0
      || Date.now() - initialState.lastChecked > deps.updateCheckStaleMs;
    if (needsRefresh) {
      await checkForUpdate();
    }

    const state = getUpdateState();
    return c.json({
      currentVersion: state.currentVersion,
      latestVersion: state.latestVersion,
      updateAvailable: isUpdateAvailable(),
      isServiceMode: state.isServiceMode,
      updateInProgress: state.updateInProgress,
      lastChecked: state.lastChecked,
    });
  });

  api.post("/update-check", async (c) => {
    await checkForUpdate();
    const state = getUpdateState();
    return c.json({
      currentVersion: state.currentVersion,
      latestVersion: state.latestVersion,
      updateAvailable: isUpdateAvailable(),
      isServiceMode: state.isServiceMode,
      updateInProgress: state.updateInProgress,
      lastChecked: state.lastChecked,
    });
  });

  api.post("/update", async (c) => {
    const state = getUpdateState();
    if (!state.isServiceMode) {
      return c.json(
        { error: "Update & restart is only available in service mode" },
        400,
      );
    }
    if (!isUpdateAvailable()) {
      return c.json({ error: "No update available" }, 400);
    }
    if (state.updateInProgress) {
      return c.json({ error: "Update already in progress" }, 409);
    }

    setUpdateInProgress(true);

    setTimeout(async () => {
      try {
        log.info(
          { targetVersion: state.latestVersion },
          "Updating zencode",
        );
        const proc = Bun.spawn(
          ["bun", "install", "-g", `zencode@${state.latestVersion}`],
          { stdout: "pipe", stderr: "pipe" },
        );
        const exitCode = await proc.exited;
        if (exitCode !== 0) {
          const stderr = await new Response(proc.stderr).text();
          log.error(
            { exitCode, stderr },
            "bun install failed",
          );
          setUpdateInProgress(false);
          return;
        }

        try {
          refreshServiceDefinition();
          log.info("Service definition refreshed");
        } catch (err) {
          log.warn({ err }, "Failed to refresh service definition");
        }

        log.info("Update successful, restarting service");

        const isLinux = process.platform === "linux";
        const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
        const restartCmd = isLinux
          ? ["systemctl", "--user", "restart", "zencode.service"]
          : uid !== undefined
            ? ["launchctl", "kickstart", "-k", `gui/${uid}/sh.zencode.app`]
            : ["launchctl", "kickstart", "-k", "sh.zencode.app"];

        Bun.spawn(restartCmd, {
          stdout: "ignore",
          stderr: "ignore",
          stdin: "ignore",
          env: isLinux
            ? {
                ...process.env,
                XDG_RUNTIME_DIR:
                  process.env.XDG_RUNTIME_DIR ||
                  `/run/user/${uid ?? 1000}`,
              }
            : undefined,
        });

        setTimeout(() => process.exit(0), 500);
      } catch (err) {
        log.error({ err }, "Update failed");
        setUpdateInProgress(false);
      }
    }, 100);

    return c.json({
      ok: true,
      message: "Update started. Server will restart shortly.",
    });
  });

  api.get("/terminal", (c) => {
    const terminalId = c.req.query("terminalId");
    const info = deps.terminalManager.getInfo(terminalId || undefined);
    if (!info) return c.json({ active: false });
    return c.json({ active: true, terminalId: info.id, cwd: info.cwd });
  });

  api.post("/terminal/spawn", async (c) => {
    const parsed = await parseBody(c, terminalSpawnSchema);
    if (!parsed.success) return parsed.response;
    const body = parsed.data;
    try {
      const terminalId = deps.terminalManager.spawn(body.cwd, body.cols, body.rows, {
        containerId: body.containerId,
      });
      return c.json({ terminalId });
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err);
      log.error({ err: message }, "Terminal spawn failed");
      return c.json({ error: message }, 500);
    }
  });

  api.post("/terminal/kill", async (c) => {
    const parsed = await parseBody(c, terminalKillSchema);
    if (!parsed.success) return parsed.response;
    deps.terminalManager.kill(parsed.data.terminalId);
    return c.json({ ok: true });
  });

  api.post("/sessions/:id/message", async (c) => {
    const id = c.req.param("id");
    const session = deps.launcher.getSession(id);
    if (!session) return c.json({ error: "Session not found" }, 404);
    if (!deps.launcher.isAlive(id)) return c.json({ error: "Session is not running" }, 400);
    const parsed = await parseBody(c, sessionMessageSchema);
    if (!parsed.success) return parsed.response;
    deps.wsBridge.injectUserMessage(id, parsed.data.content);
    return c.json({ ok: true, sessionId: id });
  });
}
