/**
 * Claudex proxy preflight + lifecycle (P1 of `mosaic yolo claudex`).
 *
 * `raine/claude-code-proxy` runs a local server on 127.0.0.1:18765 that speaks
 * the Anthropic Messages API and translates to the ChatGPT/Codex backend using
 * ChatGPT-subscription OAuth. This module owns the *preflight* and *lifecycle*
 * concerns for the launcher: is the binary present, is OAuth valid, is the proxy
 * listening, and — if not — bring it up (systemd user unit preferred, nohup
 * fallback).
 *
 * Design: every function is pure or dependency-injected so the launch path is
 * fully unit-testable without touching a real process, socket, or the OAuth
 * token. Nothing here reads `~/.config/claude-code-proxy/codex/auth.json`; the
 * proxy holds the real credential and Claude Code only ever sees
 * `ANTHROPIC_AUTH_TOKEN=unused`. Parsed auth status is deliberately coarse
 * (state + optional expiry) so no token material can be retained or surfaced.
 */

import { execFileSync, spawn, spawnSync } from 'node:child_process';
import { mkdirSync, readFileSync, readlinkSync, realpathSync, writeFileSync } from 'node:fs';
import { homedir } from 'node:os';
import { dirname, join } from 'node:path';

// ─── Endpoint / command constants (spec table) ──────────────────────────────

export const CLAUDEX_PROXY_HOST = '127.0.0.1';
export const CLAUDEX_PROXY_PORT = 18765;
export const CLAUDEX_PROXY_URL = `http://${CLAUDEX_PROXY_HOST}:${CLAUDEX_PROXY_PORT}`;
export const CLAUDEX_PROXY_BINARY = 'claude-code-proxy';
export const CLAUDEX_SYSTEMD_UNIT = 'claude-code-proxy.service';

/**
 * The proxy's dedicated liveness endpoint. We probe this — NOT the root path —
 * for two reasons: (1) the root returns non-2xx (spec gotcha #1), which is why
 * the original `curl -f` check spawned duplicate proxies; `/healthz` returns 2xx
 * when the proxy is healthy. (2) It is a *proxy-specific* contract, so a 2xx here
 * is a much stronger signal that the responder on :18765 is actually our proxy
 * and not some other local process squatting the port (CWE-345).
 */
export const CLAUDEX_HEALTH_PATH = '/healthz';
export const CLAUDEX_HEALTH_URL = `${CLAUDEX_PROXY_URL}${CLAUDEX_HEALTH_PATH}`;

/** argv for `claude-code-proxy codex auth status`. */
export function buildAuthStatusArgs(): string[] {
  return ['codex', 'auth', 'status'];
}

/** argv for `claude-code-proxy codex auth device` (device-code re-auth flow). */
export function buildDeviceAuthArgs(): string[] {
  return ['codex', 'auth', 'device'];
}

/** argv for `claude-code-proxy serve --no-monitor`. */
export function buildServeArgs(): string[] {
  return ['serve', '--no-monitor'];
}

// ─── Types ──────────────────────────────────────────────────────────────────

export type AuthState = 'valid' | 'expired' | 'unauthenticated' | 'unknown';

/**
 * Coarse OAuth status. Intentionally carries NO token material — only a state
 * and an optional best-effort expiry-in-days for user-facing messaging.
 */
export interface AuthStatus {
  state: AuthState;
  expiresInDays?: number;
}

export interface ProxyRunResult {
  status: number | null;
  stdout: string;
  stderr: string;
}

/** Runs a command synchronously and returns its captured result. */
export type CommandRunner = (cmd: string, args: string[]) => ProxyRunResult;

/** Minimal fetch shape used for the liveness probe (any HTTP response = alive). */
export type FetchLike = (
  url: string,
  init?: { signal?: AbortSignal },
) => Promise<{ status?: number }>;

// ─── Binary presence ─────────────────────────────────────────────────────────

function defaultWhich(cmd: string): string | null {
  try {
    return execFileSync('which', [cmd], { encoding: 'utf8' }).trim() || null;
  } catch {
    return null;
  }
}

export function checkProxyBinary(resolve: (cmd: string) => string | null = defaultWhich): {
  present: boolean;
  path: string | null;
} {
  const path = resolve(CLAUDEX_PROXY_BINARY);
  return { present: path !== null && path !== '', path: path || null };
}

// ─── Auth status ─────────────────────────────────────────────────────────────

/**
 * Parse `claude-code-proxy codex auth status` output into a coarse state.
 *
 * The proxy's exact wording is not contractually pinned, so this matches
 * tolerantly on well-known markers and falls back on the exit code. It never
 * copies the raw output onto the result — only a state and an optional expiry —
 * so token-shaped strings in the output cannot leak downstream.
 */
export function parseAuthStatus(result: ProxyRunResult): AuthStatus {
  const text = `${result.stdout}\n${result.stderr}`.toLowerCase();

  const expired = /\bexpired\b|token has expired|expires?d? \d+ days? ago/.test(text);
  const unauth =
    /not authenticated|not logged in|no (?:auth|credentials|token)|please (?:log ?in|authenticate)|run .*auth device/.test(
      text,
    );
  const authed = /\bauthenticated\b|logged in|token valid|valid until|expires? in/.test(text);

  let state: AuthState;
  if (expired) {
    state = 'expired';
  } else if (unauth) {
    state = 'unauthenticated';
  } else if (authed && result.status === 0) {
    // A `null` status means the check was killed by a signal — an INCOMPLETE
    // run. We require a clean exit 0 for `valid`; a partially-flushed auth line
    // from a signal-terminated check must never be trusted (finding #3).
    state = 'valid';
  } else if (result.status === 0) {
    state = 'valid';
  } else {
    state = 'unknown';
  }

  const status: AuthStatus = { state };
  const days = /expires? in (\d+) days?/.exec(text);
  if (state === 'valid' && days) {
    status.expiresInDays = Number(days[1]);
  }
  return status;
}

function defaultRun(cmd: string, args: string[]): ProxyRunResult {
  const r = spawnSync(cmd, args, { encoding: 'utf8' });
  return { status: r.status, stdout: r.stdout ?? '', stderr: r.stderr ?? '' };
}

export function checkAuthStatus(run: CommandRunner = defaultRun): AuthStatus {
  return parseAuthStatus(run(CLAUDEX_PROXY_BINARY, buildAuthStatusArgs()));
}

/** Spawn shape for the interactive device re-auth flow. */
export type InheritSpawn = (
  cmd: string,
  args: string[],
  opts: { stdio: 'inherit' },
) => { status: number | null };

function defaultInheritSpawn(cmd: string, args: string[], opts: { stdio: 'inherit' }) {
  return spawnSync(cmd, args, opts);
}

/**
 * Run the device-code re-auth flow (`claude-code-proxy codex auth device`).
 *
 * Deliberately `stdio: 'inherit'` so the device code the proxy prints goes
 * straight to the user's terminal — the launcher NEVER captures, stores, or logs
 * it, and never observes the resulting OAuth token (the proxy persists that to
 * its own config). Returns the child's exit status; 1 on an absent binary.
 */
export function runDeviceReauth(spawnImpl: InheritSpawn = defaultInheritSpawn): number {
  const r = spawnImpl(CLAUDEX_PROXY_BINARY, buildDeviceAuthArgs(), { stdio: 'inherit' });
  return r.status ?? 1;
}

// ─── Liveness (probe the proxy-specific /healthz; require 2xx) ────────────────

/**
 * Probe the proxy for liveness by hitting its dedicated `GET /healthz` endpoint
 * and requiring a 2xx response.
 *
 * This is a LIVENESS check only — it answers "is a healthy proxy responding?",
 * not "is that responder actually ours?". Requiring a 2xx on the proxy's own
 * `/healthz` contract (rather than "any HTTP response = alive") resolves spec
 * gotcha #1: the root path returns non-2xx, but `/healthz` returns 2xx when
 * healthy, so a live proxy is never mistaken for dead and no duplicate proxy is
 * spawned.
 *
 * Residual risk (CWE-345): the proxy binds loopback with NO client
 * authentication, so on a shared host a local process could occupy :18765 and
 * serve a 2xx here. A 2xx therefore does NOT by itself establish that the
 * listener is our proxy. Identity is verified SEPARATELY and at every trust
 * point by {@link verifyListenerIdentity} (OS-level uid + executable check),
 * which fails closed when identity can't be established. See
 * {@link ensureProxyRunning}. (Broader multi-user hardening — a persistent
 * warning when a foreign listener is seen — is tracked for a later phase.)
 */
export async function probeLiveness(
  url: string = CLAUDEX_HEALTH_URL,
  fetchImpl: FetchLike = fetch as unknown as FetchLike,
  timeoutMs = 1500,
): Promise<boolean> {
  const controller = new AbortController();
  let timer: ReturnType<typeof setTimeout> | undefined;

  // Bound the probe with our own timeout race rather than trusting the fetch
  // implementation to honor the abort signal — a hung socket (or a fetch that
  // ignores the signal) must never wedge the launcher. We still abort() so a
  // signal-aware fetch tears the request down promptly.
  const timeout = new Promise<boolean>((resolve) => {
    timer = setTimeout(() => {
      controller.abort();
      resolve(false);
    }, timeoutMs);
  });

  const probe = fetchImpl(url, { signal: controller.signal })
    .then((res) => typeof res.status === 'number' && res.status >= 200 && res.status < 300)
    .catch(() => false); // connection refused / aborted → dead

  try {
    return await Promise.race([probe, timeout]);
  } finally {
    if (timer) clearTimeout(timer);
  }
}

// ─── Listener identity (OS-level, CWE-345 mitigation) ─────────────────────────

/**
 * The result of verifying who actually owns the :18765 listener.
 *  - `ok`           — same-user process running the expected proxy binary.
 *  - `foreign-user` — a process owned by a DIFFERENT uid holds the port.
 *  - `wrong-exe`    — same-user, but the executable is not the proxy.
 *  - `unknown`      — identity could not be established (fail closed).
 */
export type ListenerVerdict = 'ok' | 'foreign-user' | 'wrong-exe' | 'unknown';

/** OS-level identity of the process bound to the proxy port. */
export interface ListenerIdentity {
  pid: number;
  uid: number;
  /** Absolute path of the process executable, or null if unreadable. */
  exePath: string | null;
}

export interface VerifyListenerDeps {
  /** Resolve the process bound to the proxy port (null → unidentifiable). */
  identify?: () => ListenerIdentity | null;
  /** The current process uid (-1 when unavailable, e.g. non-posix). */
  currentUid?: () => number;
  /** The expected proxy executable path (null when it can't be resolved). */
  expectedExe?: () => string | null;
  /** Canonicalize a path (resolve symlinks); null when it can't be resolved. */
  canonicalize?: (p: string) => string | null;
}

/** Resolve a path through symlinks to its canonical form; null on any failure. */
function defaultCanonicalize(p: string): string | null {
  try {
    return realpathSync(p);
  } catch {
    return null;
  }
}

/**
 * Identify the process listening on the proxy port via `ss` + `/proc`. Every
 * failure path returns null so the caller fails closed. Reads no credential
 * material — only pid/uid/exe path of the listener.
 */
function defaultIdentifyListener(port: number = CLAUDEX_PROXY_PORT): ListenerIdentity | null {
  try {
    const out = execFileSync('ss', ['-H', '-ltnp', `sport = :${port}`], { encoding: 'utf8' });
    const pidMatch = /pid=(\d+)/.exec(out);
    if (!pidMatch) return null;
    const pid = Number(pidMatch[1]);
    if (!Number.isInteger(pid) || pid <= 0) return null;

    const status = readFileSync(`/proc/${pid}/status`, 'utf8');
    const uidLine = /^Uid:\s*(\d+)/m.exec(status);
    if (!uidLine) return null;
    const uid = Number(uidLine[1]);

    let exePath: string | null = null;
    try {
      exePath = readlinkSync(`/proc/${pid}/exe`);
    } catch {
      exePath = null;
    }
    return { pid, uid, exePath };
  } catch {
    return null;
  }
}

/**
 * Verify that the process owning :18765 is genuinely OUR proxy before trusting
 * it. The proxy binds loopback with NO client authentication, so on a shared
 * host any local process could squat the port and a liveness 2xx alone does not
 * prove identity (CWE-345). We FAIL CLOSED (`unknown`) whenever identity cannot
 * be established. This needs no upstream shared-secret/unix-socket support from
 * `claude-code-proxy`.
 *
 * The executable path is the trust boundary that matters: on a shared-uid host
 * (every agent session runs as the same operator) same-uid is NOT sufficient, so
 * we require an EXACT canonical-path match against our resolved proxy binary and
 * canonicalize both sides for symlinks. There is deliberately NO basename
 * fallback — a same-uid process running `/tmp/claude-code-proxy` (right name,
 * wrong path) must never be trusted. If our own binary path can't be resolved,
 * or either path can't be canonicalized, we fail closed rather than downgrade to
 * a weaker check.
 */
export function verifyListenerIdentity(deps: VerifyListenerDeps = {}): ListenerVerdict {
  const identify = deps.identify ?? (() => defaultIdentifyListener());
  const currentUid =
    deps.currentUid ?? (() => (typeof process.getuid === 'function' ? process.getuid() : -1));
  const expectedExe = deps.expectedExe ?? (() => checkProxyBinary().path);
  const canonicalize = deps.canonicalize ?? defaultCanonicalize;

  const id = identify();
  if (!id) return 'unknown'; // can't see the listener → don't trust it
  const uid = currentUid();
  if (uid < 0) return 'unknown'; // can't establish our own identity → fail closed
  if (id.uid !== uid) return 'foreign-user'; // someone else's process holds the port
  if (!id.exePath) return 'unknown'; // can't confirm the executable → fail closed

  const expected = expectedExe();
  if (!expected) return 'unknown'; // can't resolve our own binary → fail closed
  const expectedReal = canonicalize(expected);
  const actualReal = canonicalize(id.exePath);
  if (!expectedReal || !actualReal) return 'unknown'; // uncanonicalizable → fail closed
  return actualReal === expectedReal ? 'ok' : 'wrong-exe';
}

// ─── systemd user unit ───────────────────────────────────────────────────────

export function systemdUnitPath(home: string = homedir()): string {
  return join(home, '.config', 'systemd', 'user', CLAUDEX_SYSTEMD_UNIT);
}

/**
 * Validate a path destined for a systemd `ExecStart=` line. A raw newline (or
 * other control character) in the path would let an attacker inject arbitrary
 * unit directives (e.g. an extra `ExecStartPost=`), a CWE-74 command injection.
 * We require a plain absolute path and reject any control character outright.
 */
function validateExecPath(binaryPath: string): string {
  if (typeof binaryPath !== 'string' || binaryPath.length === 0) {
    throw new Error('systemd ExecStart: binary path is empty');
  }
  if (!binaryPath.startsWith('/')) {
    throw new Error(
      `systemd ExecStart: binary path must be absolute: ${JSON.stringify(binaryPath)}`,
    );
  }

  if (/[\x00-\x1f\x7f]/.test(binaryPath)) {
    throw new Error('systemd ExecStart: binary path contains control characters');
  }
  return binaryPath;
}

/**
 * Encode a validated path for a systemd `ExecStart=` token. systemd only needs
 * quoting when the token carries whitespace or quote/backslash characters; a
 * clean path is emitted verbatim. When quoting, we escape backslashes and double
 * quotes per systemd's C-style rules so the token cannot be terminated early.
 */
function systemdQuoteExec(path: string): string {
  if (!/[\s"'\\]/.test(path)) {
    return path;
  }
  const escaped = path.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
  return `"${escaped}"`;
}

/**
 * Render the `claude-code-proxy.service` user unit. Contains no credential
 * material — the proxy reads its own OAuth token from its config dir at runtime.
 * The binary path is validated (absolute, no control characters) and systemd-
 * quoted so it cannot inject unit directives.
 */
export function buildSystemdUnitContent(binaryPath: string): string {
  const exec = `${systemdQuoteExec(validateExecPath(binaryPath))} ${buildServeArgs().join(' ')}`;
  return [
    '[Unit]',
    'Description=claude-code-proxy (Anthropic->Codex translation proxy for mosaic claudex)',
    'After=network-online.target',
    'Wants=network-online.target',
    '',
    '[Service]',
    'Type=simple',
    `ExecStart=${exec}`,
    'Restart=on-failure',
    'RestartSec=2',
    '',
    '[Install]',
    'WantedBy=default.target',
    '',
  ].join('\n');
}

/**
 * Write the user unit and reload the systemd --user daemon. Returns false when
 * systemd --user is unavailable (the caller then falls back to nohup).
 */
export function installSystemdUnit(
  binaryPath: string,
  deps: {
    home?: string;
    writeUnit?: (path: string, content: string) => void;
    run?: CommandRunner;
  } = {},
): boolean {
  const home = deps.home ?? homedir();
  const write =
    deps.writeUnit ??
    ((path: string, content: string) => {
      mkdirSync(dirname(path), { recursive: true });
      writeFileSync(path, content);
    });
  const run = deps.run ?? defaultRun;

  try {
    write(systemdUnitPath(home), buildSystemdUnitContent(binaryPath));
    const reload = run('systemctl', ['--user', 'daemon-reload']);
    return reload.status === 0;
  } catch {
    return false;
  }
}

// ─── Preflight report ────────────────────────────────────────────────────────

export interface PreflightReport {
  binaryPresent: boolean;
  binaryPath: string | null;
  auth: AuthStatus;
  live: boolean;
  /** OS-level identity verdict for the :18765 listener (`unknown` when dead). */
  listenerVerdict: ListenerVerdict;
  needsReauth: boolean;
  ok: boolean;
  problems: string[];
}

export interface PreflightDeps {
  checkBinary?: () => { present: boolean; path: string | null };
  checkAuth?: () => AuthStatus;
  probe?: () => Promise<boolean>;
  verifyListener?: () => ListenerVerdict;
}

/**
 * Compose the preflight checks into a single structured report. `ok` is true
 * only when the binary is present, OAuth is valid, the proxy responds, AND the
 * responding listener's OS-level identity verifies as our proxy.
 *
 * The identity gate lives here too, not only in {@link ensureProxyRunning}: any
 * consumer of this report (notably the phase-2 launch path) would otherwise
 * treat a `/healthz`-2xx squatter as healthy and route Claude traffic to it
 * (CWE-345). A liveness 2xx is necessary but not sufficient — a live responder
 * that fails identity fails the preflight.
 */
export async function runProxyPreflight(deps: PreflightDeps = {}): Promise<PreflightReport> {
  const checkBinary = deps.checkBinary ?? (() => checkProxyBinary());
  const checkAuth = deps.checkAuth ?? (() => checkAuthStatus());
  const probe = deps.probe ?? (() => probeLiveness());
  const verifyListener = deps.verifyListener ?? (() => verifyListenerIdentity());

  const bin = checkBinary();
  const auth = checkAuth();
  const live = await probe();
  // Only meaningful when something is actually responding; a dead port has no
  // listener identity to establish.
  const listenerVerdict: ListenerVerdict = live ? verifyListener() : 'unknown';

  const problems: string[] = [];
  if (!bin.present) {
    problems.push(
      `claude-code-proxy binary not found in PATH. Install it before launching claudex.`,
    );
  }
  const needsReauth = auth.state === 'expired' || auth.state === 'unauthenticated';
  if (needsReauth) {
    problems.push(
      `claude-code-proxy OAuth is ${auth.state}. Re-auth with: ${CLAUDEX_PROXY_BINARY} ${buildDeviceAuthArgs().join(' ')}`,
    );
  } else if (auth.state === 'unknown') {
    problems.push('Could not determine claude-code-proxy OAuth status.');
  }
  if (!live) {
    problems.push(`No proxy responding on ${CLAUDEX_PROXY_URL}.`);
  } else if (listenerVerdict !== 'ok') {
    // Non-sensitive: names the port and the verdict only — never any listener
    // command line, token, or other process detail.
    problems.push(
      `A process is listening on ${CLAUDEX_PROXY_URL} but its identity could not be verified as ${CLAUDEX_PROXY_BINARY} (${listenerVerdict}). Refusing to trust it.`,
    );
  }

  const ok = bin.present && auth.state === 'valid' && live && listenerVerdict === 'ok';
  return {
    binaryPresent: bin.present,
    binaryPath: bin.path,
    auth,
    live,
    listenerVerdict,
    needsReauth,
    ok,
    problems,
  };
}

// ─── Lifecycle: ensure the proxy is running ──────────────────────────────────

export type ProxyStartMethod = 'already' | 'systemd' | 'nohup' | 'untrusted' | 'failed';

export interface EnsureProxyResult {
  live: boolean;
  method: ProxyStartMethod;
}

/** Minimal spawned-child shape used by the nohup fallback (testable seam). */
export interface SpawnedChild {
  once(event: string, listener: (arg?: unknown) => void): unknown;
  unref(): void;
}

/** Spawn shape for the detached fallback process. */
export type SpawnLike = (
  cmd: string,
  args: string[],
  opts: { detached: boolean; stdio: 'ignore' },
) => SpawnedChild;

export interface StartNohupDeps {
  resolveBin?: () => string;
  spawnImpl?: SpawnLike;
}

/**
 * Start the proxy as a detached background process (the fallback when no systemd
 * user unit is available).
 *
 * `spawn()` reports launch failures (ENOENT/EACCES) ASYNCHRONOUSLY via the
 * child's `error` event, which a `try/catch` cannot see. If left unhandled that
 * event throws and crashes the launcher. So we: (1) attach the `error` listener
 * BEFORE `unref()`, capturing a failed launch as a non-zero result instead of a
 * crash; and (2) resolve success only after the child's `spawn` event fires —
 * never optimistically before the process is known to have started.
 */
export function startNohupProxy(deps: StartNohupDeps = {}): Promise<ProxyRunResult> {
  const resolveBin = deps.resolveBin ?? (() => checkProxyBinary().path ?? CLAUDEX_PROXY_BINARY);
  const spawnImpl =
    deps.spawnImpl ?? ((cmd, args, opts) => spawn(cmd, args, opts) as unknown as SpawnedChild);

  return new Promise<ProxyRunResult>((resolve) => {
    let settled = false;
    const finish = (r: ProxyRunResult) => {
      if (!settled) {
        settled = true;
        resolve(r);
      }
    };

    let child: SpawnedChild;
    try {
      child = spawnImpl(resolveBin(), buildServeArgs(), { detached: true, stdio: 'ignore' });
    } catch (err) {
      finish({ status: 1, stdout: '', stderr: err instanceof Error ? err.message : String(err) });
      return;
    }

    // Register error handling BEFORE unref so an async spawn failure is caught.
    child.once('error', (err) => {
      finish({
        status: 1,
        stdout: '',
        stderr: err instanceof Error ? err.message : String(err),
      });
    });
    child.once('spawn', () => {
      child.unref();
      finish({ status: 0, stdout: '', stderr: '' });
    });
  });
}

export interface EnsureProxyDeps {
  probe?: () => Promise<boolean>;
  /** OS-level identity check for the process holding the proxy port. */
  verifyListener?: () => ListenerVerdict;
  startSystemd?: () => ProxyRunResult;
  startNohup?: () => Promise<ProxyRunResult>;
  waitMs?: (ms: number) => Promise<void>;
  /** Interval between liveness polls while waiting for a start to bind. */
  settleMs?: number;
  /** Total budget to wait for a started proxy to bind its socket. */
  startupDeadlineMs?: number;
}

function defaultWait(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function defaultStartSystemd(): ProxyRunResult {
  return defaultRun('systemctl', ['--user', 'start', CLAUDEX_SYSTEMD_UNIT]);
}

/**
 * Poll for a TRUSTED-live proxy up to a bounded startup deadline. A start command
 * returning 0 only means the job was ACCEPTED, not that the socket is bound — so
 * we keep probing at `intervalMs` until either the deadline elapses or the port
 * both responds AND passes the OS-level identity check. Liveness alone is not
 * enough: a responder that fails identity (a squatter) must never be trusted.
 */
async function waitForTrusted(
  probe: () => Promise<boolean>,
  verifyListener: () => ListenerVerdict,
  waitMs: (ms: number) => Promise<void>,
  intervalMs: number,
  deadlineMs: number,
): Promise<boolean> {
  let elapsed = 0;
  while (elapsed < deadlineMs) {
    await waitMs(intervalMs);
    elapsed += intervalMs;
    if ((await probe()) && verifyListener() === 'ok') {
      return true;
    }
  }
  return false;
}

/**
 * Ensure a proxy is listening. No-op when already live. Otherwise prefer the
 * systemd user unit, then fall back to a detached background process.
 *
 * Every trust point is gated on OS-level listener identity, not just liveness:
 * the proxy has no client authentication, so on a shared host a local process
 * could squat :18765 and a 2xx `/healthz` alone would not prove it is our proxy
 * (CWE-345, finding #2). We only trust a responder whose owning process is the
 * current uid running the expected proxy binary; otherwise we fail closed.
 *
 * If a responder is already present but its identity does NOT verify, we return
 * `untrusted` WITHOUT starting anything — the port is taken, so spawning would
 * only create contention, and we must never route Claude traffic through an
 * unverified listener.
 *
 * After a start command is accepted we poll to a bounded startup deadline before
 * giving up: `systemctl start` exit 0 means the job was accepted, not that the
 * socket bound within one probe interval. Critically, once systemd ACCEPTS the
 * job we do NOT fall back to nohup even if it never becomes trusted-live in the
 * deadline (finding #1): the accepted unit may bind late or be restarted by
 * systemd, and a second proxy would then contend for :18765 — the very
 * duplicate-proxy outcome this function exists to prevent. nohup is reachable
 * only when systemd never accepted the job at all.
 */
export async function ensureProxyRunning(deps: EnsureProxyDeps = {}): Promise<EnsureProxyResult> {
  const probe = deps.probe ?? (() => probeLiveness());
  const verifyListener = deps.verifyListener ?? (() => verifyListenerIdentity());
  const startSystemd = deps.startSystemd ?? defaultStartSystemd;
  const startNohup = deps.startNohup ?? (() => startNohupProxy());
  const waitMs = deps.waitMs ?? defaultWait;
  const settleMs = deps.settleMs ?? 500;
  const startupDeadlineMs = deps.startupDeadlineMs ?? 5000;

  if (await probe()) {
    // Something answers on :18765 — trust it ONLY if it is provably our proxy.
    return verifyListener() === 'ok'
      ? { live: true, method: 'already' }
      : { live: false, method: 'untrusted' };
  }

  const systemd = startSystemd();
  if (systemd.status === 0) {
    // systemd accepted the job. Wait for a trusted-live bind, but never fall
    // back to nohup afterward — that would risk a duplicate proxy (finding #1).
    if (await waitForTrusted(probe, verifyListener, waitMs, settleMs, startupDeadlineMs)) {
      return { live: true, method: 'systemd' };
    }
    return { live: false, method: 'failed' };
  }

  const nohup = await startNohup();
  if (nohup.status === 0) {
    if (await waitForTrusted(probe, verifyListener, waitMs, settleMs, startupDeadlineMs)) {
      return { live: true, method: 'nohup' };
    }
  }

  return { live: false, method: 'failed' };
}
