import { spawn } from "node:child_process";
import { createServer } from "node:net";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { pathToFileURL } from "node:url";

const delay = (milliseconds) => new Promise((resolveDelay) => setTimeout(resolveDelay, milliseconds));

async function reserveLoopbackPort() {
  const server = createServer();
  await new Promise((resolveListen, reject) => {
    server.once("error", reject);
    server.listen(0, "127.0.0.1", resolveListen);
  });
  const port = server.address().port;
  await new Promise((resolveClose, reject) => server.close((error) => error ? reject(error) : resolveClose()));
  return port;
}

async function stopProcessTree(child) {
  if (!child || child.exitCode !== null) return;
  if (process.platform === "win32") {
    await new Promise((resolveStop) => {
      const killer = spawn("taskkill.exe", ["/pid", String(child.pid), "/t", "/f"], {
        windowsHide: true,
        stdio: "ignore"
      });
      killer.once("error", resolveStop);
      killer.once("exit", resolveStop);
    });
  } else {
    child.kill("SIGTERM");
  }
  if (child.exitCode === null) {
    await new Promise((resolveExit) => {
      const timeout = setTimeout(resolveExit, 5_000);
      child.once("exit", () => {
        clearTimeout(timeout);
        resolveExit();
      });
    });
  }
  if (child.exitCode === null) child.kill("SIGKILL");
}

async function createInstalledPairing({ nodePath, layout, configPath, origin }) {
  const child = spawn(nodePath, [join(resolve(layout), "app", "backend", "src", "device-pairing-cli.js")], {
    cwd: resolve(layout),
    env: { ...process.env, CODE_REMOTE_MANAGER_CONFIG_PATH: configPath },
    windowsHide: true,
    stdio: ["pipe", "pipe", "ignore"]
  });
  let output = "";
  child.stdout.on("data", (chunk) => {
    output += String(chunk);
    if (output.length > 4096) child.kill();
  });
  child.stdin.end(JSON.stringify({ origin }));
  const exitCode = await new Promise((resolveExit, reject) => {
    child.once("error", reject);
    child.once("exit", resolveExit);
  });
  if (exitCode !== 0 || output.length > 4096) throw new Error("Installed layout pairing command failed.");
  try {
    const pairing = JSON.parse(output);
    if (!/^[0-9A-HJKMNP-TV-Z]{4}(?:-[0-9A-HJKMNP-TV-Z]{4}){2}$/.test(pairing.code) ||
        typeof pairing.qrPayload !== "string" || !pairing.qrPayload.startsWith("crm-pair:v1:") ||
        !Number.isFinite(Date.parse(pairing.expiresAt))) {
      throw new Error("invalid");
    }
    return pairing;
  } catch {
    throw new Error("Installed layout pairing output was invalid.");
  }
}

async function verifyInstalledPairing({ nodePath, layout, configPath, pwaPort }) {
  const origin = `http://127.0.0.1:${pwaPort}`;
  const pairing = await createInstalledPairing({ nodePath, layout, configPath, origin });
  const healthResponse = await fetch(`${origin}/api/health`);
  if (!healthResponse.ok) throw new Error("Installed layout pairing health failed.");
  const health = await healthResponse.json();
  if (!health.authentication?.pairingAvailable || typeof health.authentication?.pairingAttemptId !== "string") {
    throw new Error("Installed layout pairing was not available.");
  }

  const pairResponse = await fetch(`${origin}/api/auth/pair`, {
    method: "POST",
    headers: {
      "content-type": "application/json",
      origin,
      "x-code-remote-csrf": "1"
    },
    body: JSON.stringify({ attemptId: health.authentication.pairingAttemptId, code: pairing.code, label: "Installed smoke" })
  });
  const cookie = pairResponse.headers.get("set-cookie")?.split(";", 1)[0];
  if (pairResponse.status !== 201 || !cookie) throw new Error("Installed layout pairing exchange failed.");

  const protectedResponse = await fetch(`${origin}/api/accounts`, { headers: { cookie, origin } });
  if (!protectedResponse.ok) throw new Error("Installed layout paired access failed.");
}

export async function smokeWindowsInstalledLayout({
  layout,
  nodePath = join(layout, "node.exe"),
  timeoutMs = 30_000
}) {
  const temporaryRoot = await mkdtemp(join(tmpdir(), "crm-installed-layout-smoke-"));
  let child;
  try {
    const [backendPort, pwaPort] = await Promise.all([reserveLoopbackPort(), reserveLoopbackPort()]);
    const localAppData = join(temporaryRoot, "local-app-data");
    const dataDir = join(temporaryRoot, "data");
    const workspace = join(temporaryRoot, "workspace");
    const configPath = join(temporaryRoot, "config.local.json");
    await Promise.all([
      mkdir(localAppData, { recursive: true }),
      mkdir(dataDir, { recursive: true }),
      mkdir(workspace, { recursive: true })
    ]);
    await writeFile(configPath, JSON.stringify({
      codexCliPath: "codex.cmd",
      engine: "fake",
      bindHost: "127.0.0.1",
      port: backendPort,
      allowedOrigins: [`http://127.0.0.1:${pwaPort}`],
      dataDir,
      accounts: [],
      workspaces: [{ id: "smoke", label: "Smoke", path: workspace }]
    }));

    child = spawn(nodePath, [join(resolve(layout), "host", "host.mjs")], {
      cwd: resolve(layout),
      env: {
        ...process.env,
        CODE_REMOTE_MANAGER_CONFIG_PATH: configPath,
        CODE_REMOTE_MANAGER_PWA_PORT: String(pwaPort),
        LOCALAPPDATA: localAppData
      },
      windowsHide: true,
      stdio: "ignore"
    });
    const exited = new Promise((resolveExit) => {
      child.once("error", (error) => resolveExit({ error }));
      child.once("exit", (code) => resolveExit({ code }));
    });
    const deadline = Date.now() + timeoutMs;
    let healthy = false;
    while (Date.now() < deadline) {
      const state = await Promise.race([exited, delay(100).then(() => undefined)]);
      if (state) throw new Error("Installed layout host exited before health check.");
      try {
        const response = await fetch(`http://127.0.0.1:${pwaPort}/api/health`);
        if (response.ok && (await response.json()).ok === true) {
          healthy = true;
          break;
        }
      } catch {}
    }
    if (!healthy) throw new Error("Installed layout health check timed out.");
    await verifyInstalledPairing({ nodePath, layout, configPath, pwaPort });
  } finally {
    await stopProcessTree(child);
    await rm(temporaryRoot, { recursive: true, force: true });
  }
}

const invokedPath = process.argv[1] ? pathToFileURL(resolve(process.argv[1])).href : "";
if (invokedPath === import.meta.url) {
  const [layout] = process.argv.slice(2);
  if (!layout) {
    console.error("Usage: node smoke-windows-installed-layout.mjs LAYOUT");
    process.exitCode = 1;
  } else {
    try {
      await smokeWindowsInstalledLayout({ layout: resolve(layout) });
    } catch {
      console.error("Windows installed-layout smoke failed.");
      process.exitCode = 1;
    }
  }
}
