import { createHash } from "node:crypto";
import { boxGcScript } from "./remote-build-gc.mjs";

export const GC_SCRIPT_REMOTE = ".local/bin/rb-mirror-gc";
export const GC_UNIT_DIR = ".config/systemd/user";

export function gcUnitFiles(intervalHours) {
  if (!Number.isInteger(intervalHours) || intervalHours < 1) throw new Error(`invalid gc_interval_hours: ${intervalHours}`);
  return {
    service: `[Unit]
Description=Remote build mirror garbage collection

[Service]
Type=oneshot
ExecStart=%h/${GC_SCRIPT_REMOTE}
Nice=15
IOSchedulingClass=idle
`,
    timer: `[Unit]
Description=Remote build mirror garbage collection

[Timer]
OnBootSec=10min
OnUnitActiveSec=${intervalHours}h
Persistent=true

[Install]
WantedBy=timers.target
`,
  };
}

export function gcScriptSha(cfg) {
  return createHash("sha256").update(boxGcScript(cfg)).digest("hex");
}

export function gcInstallScript(cfg) {
  const script = boxGcScript(cfg);
  const units = gcUnitFiles(Number(cfg.gc_interval_hours ?? 1));
  return `set -eu
mkdir -p ~/.local/bin ~/${GC_UNIT_DIR}
cat > ~/${GC_SCRIPT_REMOTE} <<'RBGC_EOF'
${script.endsWith("\n") ? script : `${script}\n`}RBGC_EOF
chmod +x ~/${GC_SCRIPT_REMOTE}
cat > ~/${GC_UNIT_DIR}/rb-mirror-gc.service <<'RBGC_EOF'
${units.service}RBGC_EOF
cat > ~/${GC_UNIT_DIR}/rb-mirror-gc.timer <<'RBGC_EOF'
${units.timer}RBGC_EOF
systemctl --user daemon-reload
systemctl --user enable --now rb-mirror-gc.timer >/dev/null
sha256sum ~/${GC_SCRIPT_REMOTE} | cut -d' ' -f1
`;
}

// The box script is a materialization of boxGcScript(), so a change to the generator reaches the
// box only through this installer. Rewriting unconditionally would restart the timer on every
// call, so the remote hash decides — and the timer's armed state is checked even when it matches,
// because a current script that nothing runs collects nothing.
export function installBoxGc({ cfg, ssh, log = () => {} }) {
  const want = gcScriptSha(cfg);
  // `is-enabled` exits non-zero for every state that is not "enabled" — 4 when the unit is absent,
  // which is exactly a box that has never been installed. State is read from stdout, so the remote
  // side always exits 0 and a non-zero status here means the ssh transport itself failed.
  const probe = ssh(cfg, `sha256sum ~/${GC_SCRIPT_REMOTE} 2>/dev/null | cut -d' ' -f1; systemctl --user is-enabled rb-mirror-gc.timer 2>/dev/null || true`, { timeoutSec: 30 });
  if (probe.status !== 0) return { host: cfg.host, ok: false, reason: `probe failed rc=${probe.status ?? "?"}` };
  const [have = "", armed = ""] = String(probe.stdout ?? "").trim().split("\n");
  if (have === want && armed === "enabled") return { host: cfg.host, ok: true, changed: false, sha: want };

  const result = ssh(cfg, gcInstallScript(cfg), { timeoutSec: 120 });
  if (result.status !== 0) return { host: cfg.host, ok: false, reason: `install failed rc=${result.status ?? "?"}` };
  const got = String(result.stdout ?? "").trim().split("\n").pop();
  if (got !== want) return { host: cfg.host, ok: false, reason: `hash mismatch after write: ${got}` };
  log(`box gc installed host=${cfg.host} sha=${want.slice(0, 12)}`);
  return { host: cfg.host, ok: true, changed: true, sha: want };
}

export function hostConfigs(baseCfg) {
  return baseCfg.hosts.map((host) => ({ ...baseCfg, host }));
}
