import { readFileSync, renameSync, rmSync, statSync, utimesSync } from "node:fs";
import { resolve } from "node:path";

const root = process.cwd();
const watchedPath = resolve(root, "scripts/caserun-native-fixtures.ts");
const displacedPath = `${watchedPath}.reload-smoke`;
const originalStat = statSync(watchedPath);
const port = Number(process.env.HWLAB_CASERUN_RELOAD_SMOKE_PORT || 14316);
const baseUrl = `http://127.0.0.1:${port}`;
const reloadEvents: Array<{ pid?: number; reason?: string }> = [];
let output = "";
let displaced = false;

function requireCondition(condition: unknown, message: string): asserts condition {
  if (!condition) throw new Error(message);
}

async function waitFor(description: string, condition: () => boolean | Promise<boolean>, timeoutMs = 5000): Promise<void> {
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    if (await condition()) return;
    await Bun.sleep(50);
  }
  throw new Error(`timeout waiting for ${description}`);
}

async function readOutput(stream: ReadableStream<Uint8Array>): Promise<void> {
  const reader = stream.getReader();
  const decoder = new TextDecoder();
  let pending = "";
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    const chunk = decoder.decode(value, { stream: true });
    output += chunk;
    pending += chunk;
    const lines = pending.split("\n");
    pending = lines.pop() || "";
    for (const line of lines) {
      if (!line.includes('"event":"caserun-native-reload"')) continue;
      const event = JSON.parse(line) as { pid?: number; reason?: string };
      if (!reloadEvents.some((existing) => existing.pid === event.pid)) reloadEvents.push(event);
    }
  }
  const finalChunk = decoder.decode();
  output += finalChunk;
  pending += finalChunk;
  if (pending.includes('"event":"caserun-native-reload"')) {
    const event = JSON.parse(pending) as { pid?: number; reason?: string };
    if (!reloadEvents.some((existing) => existing.pid === event.pid)) reloadEvents.push(event);
  }
}

async function healthAvailable(): Promise<boolean> {
  try {
    const response = await fetch(`${baseUrl}/health`);
    return response.ok;
  } catch {
    return false;
  }
}

function childPids(supervisorPid: number): number[] {
  try {
    return readFileSync(`/proc/${supervisorPid}/task/${supervisorPid}/children`, "utf8")
      .trim()
      .split(/\s+/)
      .filter(Boolean)
      .map(Number);
  } catch {
    return [];
  }
}

rmSync(displacedPath, { force: true });
const supervisor = Bun.spawn([process.execPath, "scripts/caserun-native-supervisor.ts"], {
  cwd: root,
  env: { ...process.env, HWLAB_CASERUN_NATIVE_HOST: "127.0.0.1", HWLAB_CASERUN_NATIVE_PORT: String(port) },
  stdin: "ignore",
  stdout: "pipe",
  stderr: "pipe"
});
const stdoutTask = readOutput(supervisor.stdout);
const stderrTask = readOutput(supervisor.stderr);

try {
  await waitFor("initial native health", healthAvailable);

  renameSync(watchedPath, displacedPath);
  displaced = true;
  await Bun.sleep(300);
  requireCondition(supervisor.exitCode === null, `supervisor exited during atomic save window: ${supervisor.exitCode}`);
  renameSync(displacedPath, watchedPath);
  displaced = false;
  const restoredAt = new Date(Date.now() + 1000);
  utimesSync(watchedPath, restoredAt, restoredAt);
  await waitFor("reload after atomic save", () => reloadEvents.length >= 2);

  for (let index = 0; index < 5; index += 1) {
    const changedAt = new Date(Date.now() + (index + 1) * 1000);
    utimesSync(watchedPath, changedAt, changedAt);
    await Bun.sleep(120);
  }

  await waitFor("final native health", healthAvailable);
  await Bun.sleep(300);
  const liveChildren = childPids(supervisor.pid);
  requireCondition(liveChildren.length === 1, `expected one native server child, found ${liveChildren.join(",") || "none"}`);
  requireCondition(reloadEvents.length >= 3, `expected repeated reloads, found ${reloadEvents.length}`);
  requireCondition(!output.includes("EADDRINUSE"), "native reload logged EADDRINUSE");

  console.log(JSON.stringify({
    ok: true,
    mode: "native-test",
    supervisorPid: supervisor.pid,
    serverPid: liveChildren[0],
    reloadCount: reloadEvents.length,
    reloadPids: reloadEvents.map((event) => event.pid),
    atomicSaveMissingFileTolerated: true,
    eaddrinuse: false
  }));
} finally {
  if (displaced) renameSync(displacedPath, watchedPath);
  rmSync(displacedPath, { force: true });
  utimesSync(watchedPath, originalStat.atime, originalStat.mtime);
  if (supervisor.exitCode === null) supervisor.kill("SIGTERM");
  await supervisor.exited;
  await Promise.all([stdoutTask, stderrTask]);
}
