import { readFileSync } from "node:fs";
import { join } from "node:path";
import { readPid, isAlive } from "../../lifecycle/pid.ts";
import { paths } from "../../lifecycle/paths.ts";

// `fewtok restart --graceful` — zero-downtime rolling restart. Sends SIGUSR2 to the
// supervisor, which spawns a fresh child on the shared reusePort socket, waits for its
// readiness handshake, then retires the old child. The session-facing socket never
// drops: the kernel routes new connections across both listeners during the handoff.
//
// Only the supervisor handles SIGUSR2 as "restart". A bare `--foreground` proxy (no
// supervisor) treats SIGUSR2 as become-primary (a no-op when already primary) and emits
// no completion line — detected here as a timeout with a clear, actionable message
// rather than a confusing hang.
export async function cmdRestart(flags: Record<string, string | true>, verbose = false): Promise<number> {
  if (flags.graceful !== true) {
    if (!verbose) { console.log("err=use-graceful"); } else {
      console.error("fewtok restart requires --graceful (the only supported, zero-downtime mode)");
    }
    return 1;
  }

  const pid = readPid(paths.pidFile());
  if (pid === null || !isAlive(paths.pidFile())) {
    if (!verbose) { console.log("err=not-running"); } else { console.error("fewtok is not running (no live supervisor pid)"); }
    return 1;
  }

  // Snapshot the log tail offset so we only react to lines emitted after our signal.
  const logPath = join(paths.logs(), `proxy-${new Date().toISOString().slice(0, 10)}.log`);
  let offset = 0;
  try { offset = readFileSync(logPath, "utf8").length; } catch { /* log may not exist yet */ }

  try {
    process.kill(pid, "SIGUSR2");
  } catch (err) {
    if (!verbose) { console.log("err=restart-failed"); } else { console.error(`failed to send SIGUSR2 to pid ${pid}:`, err); }
    return 1;
  }

  const result = await waitForRestart(logPath, offset, 15_000);
  if (result === "complete") {
    if (!verbose) { console.log("ok"); } else { console.log(`fewtok gracefully restarted (supervisor pid ${pid})`); }
    return 0;
  }
  if (result === "aborted") {
    if (!verbose) { console.log("err=restart-aborted"); } else {
      console.error("graceful restart aborted: incoming child failed to come up — old child still serving");
    }
    return 1;
  }
  if (!verbose) { console.log("err=restart-unconfirmed"); } else {
    console.error(
      "graceful restart not confirmed within 15s — is fewtok running under the supervisor " +
        "(started with `fewtok start --background`)? A bare `--foreground` proxy has no SIGUSR2 restart handler.",
    );
  }
  return 1;
}

// Confirm the rolling restart from the supervisor's log. The supervisor logs
// "graceful restart complete" only after the new primary is promoted, and
// "graceful restart aborted" if the incoming child never became ready / exited.
async function waitForRestart(logPath: string, offset: number, timeoutMs: number): Promise<"complete" | "aborted" | "timeout"> {
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    await new Promise((r) => setTimeout(r, 100));
    let tail = "";
    try { tail = readFileSync(logPath, "utf8").slice(offset); } catch { /* retry next tick */ }
    if (tail.includes("graceful restart complete")) return "complete";
    if (tail.includes("graceful restart aborted")) return "aborted";
  }
  return "timeout";
}
