#!/usr/bin/env node
// Converges the adapter credential set onto every buildbox before a seat is dispatched.
//
// The source is always the workstation's LIVE credential path resolved through its symlinks:
// `~/.codex/auth.json` is a symlink systray repoints at the active account's store, so following
// it yields the account systray currently has active — no second registry, no account name in
// this file. A vendor-managed file with no systray link (cursor) resolves to itself.
//
// Distributing copies is safe: a rotated-away refresh token stays valid, so each node refreshes
// its own copy independently and drift is re-derived from the workstation on the next dispatch.
//
// Steady state costs nothing: a per-host stamp holds the digests last converged, and a host whose
// stamp matches every local digest is skipped without opening a connection.
import { createHash } from "node:crypto";
import { mkdirSync, readFileSync, realpathSync, renameSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

const HERE = dirname(fileURLToPath(import.meta.url));
const HOME = homedir();

export const MANIFEST_PATH = process.env.HARNESS_SEAT_CREDENTIALS || join(HERE, "credentials.json");

export function loadManifest(path = MANIFEST_PATH) {
  const manifest = JSON.parse(readFileSync(path, "utf8"));
  if (manifest.version !== "seat-credentials/v1") throw new Error(`unsupported manifest version ${manifest.version}`);
  if (!Array.isArray(manifest.items) || !manifest.items.length) throw new Error("manifest declares no items");
  for (const item of manifest.items) {
    if (!item.id || !item.home || !item.seed) throw new Error(`manifest item missing id/home/seed: ${JSON.stringify(item)}`);
    if (item.home.startsWith("/") || item.home.includes("..")) throw new Error(`manifest home must be a relative path under $HOME: ${item.home}`);
  }
  return manifest;
}

function sha256(buf) { return createHash("sha256").update(buf).digest("hex"); }

// A dangling symlink and an absent file are the same fact here: this box has nothing to ship.
export function localCredentials(manifest, home = HOME) {
  const found = [];
  for (const item of manifest.items) {
    const path = join(home, item.home);
    let real;
    try { real = realpathSync(path); } catch { continue; }
    let content;
    try { content = readFileSync(real); } catch { continue; }
    found.push({ id: item.id, home: item.home, content, sha: sha256(content) });
  }
  return found;
}

function safeHost(host) { return String(host).replace(/[^a-zA-Z0-9._-]/g, "_"); }
function stampPath(stateDir, host) { return join(stateDir, `seat-creds-${safeHost(host)}.json`); }

function readStamp(stateDir, host) {
  try { return JSON.parse(readFileSync(stampPath(stateDir, host), "utf8")); } catch { return null; }
}

function writeStamp(stateDir, host, items) {
  try {
    mkdirSync(stateDir, { recursive: true });
    const path = stampPath(stateDir, host);
    const tmp = `${path}.tmp`;
    writeFileSync(tmp, `${JSON.stringify({ host, ts: Date.now(), items })}\n`);
    renameSync(tmp, path);
  } catch {}
}

export function stampSatisfied(stamp, creds) {
  if (!stamp || typeof stamp.items !== "object" || stamp.items === null) return false;
  return creds.every((cred) => stamp.items[cred.id] === cred.sha);
}

function shq(value) { return `'${String(value).replace(/'/g, `'\\''`)}'`; }

// One round trip for the whole set. A path that does not exist simply produces no line.
export function parseRemoteDigests(stdout, creds) {
  const byPath = new Map();
  for (const line of String(stdout || "").split("\n")) {
    const match = /^([0-9a-f]{64})\s+(.+?)\s*$/.exec(line);
    if (match) byPath.set(match[2], match[1]);
  }
  const out = {};
  for (const cred of creds) out[cred.id] = byPath.get(cred.home) || null;
  return out;
}

function remoteDigests(transport, cfg, creds) {
  const paths = creds.map((cred) => shq(cred.home)).join(" ");
  const result = transport.ssh(cfg, `cd ~ && sha256sum ${paths} 2>/dev/null; exit 0`, { timeoutSec: 30 });
  if (result.status !== 0) return null;
  return parseRemoteDigests(result.stdout, creds);
}

// Written through a private temp sibling and renamed, so a seat that reads the path mid-push
// sees either the old file or the new one, never a truncated token. The content only ever exists
// on the wire and at its final 0600 destination — never in a shared staging directory.
function pushCredential(transport, cfg, cred) {
  const target = shq(cred.home);
  const tmp = shq(`${cred.home}.seat-creds.tmp`);
  const command = [
    "cd ~",
    `mkdir -p ${shq(dirname(cred.home))}`,
    `umask 077`,
    `cat > ${tmp}`,
    `chmod 0600 ${tmp}`,
    `mv -f ${tmp} ${target}`,
    `sha256sum ${target}`,
  ].join(" && ");
  const result = transport.ssh(cfg, command, { timeoutSec: 60, input: cred.content });
  if (result.status !== 0) return { ok: false, detail: `push failed rc=${result.status}` };
  const digest = parseRemoteDigests(result.stdout, [cred])[cred.id];
  if (digest !== cred.sha) return { ok: false, detail: "post-push digest mismatch" };
  return { ok: true };
}

// Best effort by contract: a host that cannot be converged is reported and left alone. The
// fail-closed half of this design is seat-run.sh, which refuses a dispatch on a box whose
// credential is absent — this half only keeps boxes from getting there.
export function convergeHost(transport, baseConfig, host, creds, stateDir, log) {
  if (stampSatisfied(readStamp(stateDir, host), creds)) return { host, skipped: true, pushed: [] };
  const cfg = { ...baseConfig, host };
  const remote = remoteDigests(transport, cfg, creds);
  if (!remote) { log(`seat creds: ${host} unreachable — leaving its credentials alone`); return { host, unreachable: true, pushed: [] }; }

  const pushed = [];
  const converged = {};
  for (const cred of creds) {
    if (remote[cred.id] === cred.sha) { converged[cred.id] = cred.sha; continue; }
    const result = pushCredential(transport, cfg, cred);
    if (!result.ok) { log(`seat creds: ${host} ${cred.id} ${result.detail}`); continue; }
    converged[cred.id] = cred.sha;
    pushed.push(cred.id);
    log(`seat creds: ${host} ${cred.home} -> ${cred.sha.slice(0, 12)}`);
  }
  if (creds.every((cred) => converged[cred.id] === cred.sha)) writeStamp(stateDir, host, converged);
  return { host, pushed };
}

export function convergeCredentials({ transport, stateDir, log = () => {} }) {
  let baseConfig;
  try { baseConfig = transport.loadRemoteConfig(); } catch { return []; }
  if (!baseConfig || !Array.isArray(baseConfig.hosts) || !baseConfig.hosts.length) return [];

  let creds;
  try { creds = localCredentials(loadManifest()); } catch (err) { log(`seat creds: manifest unusable — ${err.message}`); return []; }
  if (!creds.length) { log("seat creds: no credentials present on this workstation"); return []; }

  return baseConfig.hosts.map((host) => convergeHost(transport, baseConfig, host, creds, stateDir, log));
}
