import { spawn, spawnSync } from "node:child_process";
import { existsSync, mkdirSync, rmSync } from "node:fs";
import path from "node:path";

const bootRepo = requiredEnv("HWLAB_BOOT_REPO");
const bootCommit = requiredEnv("HWLAB_BOOT_COMMIT");
const bootSh = requiredEnv("HWLAB_BOOT_SH");
const bootRef = requiredEnv("HWLAB_BOOT_REF");
const checkoutDir = process.env.HWLAB_BOOT_CHECKOUT_DIR || "/workspace/hwlab-boot/repo";

if (!/^[a-f0-9]{40}$/u.test(bootCommit)) fail("HWLAB_BOOT_COMMIT must be a full 40-char SHA");
if (path.isAbsolute(bootSh) || bootSh.split("/").includes("..")) fail("HWLAB_BOOT_SH must be repo-relative");
if (!/^[A-Za-z0-9._/-]+$/u.test(bootRef) || bootRef.startsWith("-") || bootRef.includes("..")) {
  fail("HWLAB_BOOT_REF must be a safe branch or ref name");
}

const readUrl = process.env.HWLAB_BOOT_READ_URL || mirrorReadUrl(bootRepo);
mkdirSync(path.dirname(checkoutDir), { recursive: true });
rmSync(checkoutDir, { recursive: true, force: true });
// Go env images avoid npm dependency installation; Go modules stay in Docker/BuildKit layers.
run("git", ["clone", "--no-checkout", "--single-branch", "--branch", bootRef, readUrl, checkoutDir], "/");
run("git", ["checkout", "--detach", bootCommit], checkoutDir);

const scriptPath = path.join(checkoutDir, bootSh);
if (!existsSync(scriptPath)) fail(`boot script not found: ${bootSh}`);
run("sh", [scriptPath], checkoutDir, { replace: true });

function requiredEnv(name) {
  const value = process.env[name];
  if (!value) fail(`${name} is required`);
  return value;
}

function mirrorReadUrl(repo) {
  if (/github\.com[:/]pikasTech\/HWLAB(?:\.git)?$/u.test(repo)) {
    return "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git";
  }
  fail("no mirror resolver for HWLAB_BOOT_REPO; refusing direct runtime GitHub fallback");
}

function run(command, args, cwd, options = {}) {
  if (options.replace) {
    runReplacingProcess(command, args, cwd);
    return;
  }
  const result = spawnSync(command, args, { cwd, env: process.env, stdio: "inherit" });
  if (result.error) fail(`${command} failed to start: ${result.error.message}`);
  if (result.signal) process.kill(process.pid, result.signal);
  const code = result.status ?? 0;
  if (code !== 0) process.exit(code);
}

function runReplacingProcess(command, args, cwd) {
  const child = spawn(command, args, { cwd, env: process.env, stdio: "inherit", detached: true });
  let settled = false;
  const signalHandlers = [];
  const cleanup = () => {
    for (const { signal, handler } of signalHandlers.splice(0)) process.off(signal, handler);
  };
  const forwardSignal = (signal) => {
    if (!child.pid) return;
    try {
      process.kill(-child.pid, signal);
    } catch {
      try { child.kill(signal); } catch {}
    }
  };
  for (const signal of ["SIGINT", "SIGTERM"]) {
    const handler = () => forwardSignal(signal);
    process.on(signal, handler);
    signalHandlers.push({ signal, handler });
  }
  child.on("error", (error) => {
    if (settled) return;
    settled = true;
    cleanup();
    fail(`${command} failed to start: ${error.message}`);
  });
  child.on("exit", (code, signal) => {
    if (settled) return;
    settled = true;
    cleanup();
    if (signal) process.exit(signalExitCode(signal));
    process.exit(code ?? 0);
  });
}

function signalExitCode(signal) {
  const signals = { SIGHUP: 1, SIGINT: 2, SIGQUIT: 3, SIGTERM: 15 };
  return 128 + (signals[signal] ?? 1);
}

function fail(message) {
  process.stderr.write(`${JSON.stringify({ status: "failed", error: "hwlab_go_env_reuse_launcher_failed", message })}\n`);
  process.exit(1);
}
