/**
 * kubectl-wrap.ts — subprocess timeout wrapper for kubectl invocations.
 *
 * Phase 1b C1 of olam-host-suite-phase-1b-k3s-beta-flavour (plan
 * ~/.claude/plans/olam-host-suite-phase-1b-k3s-beta-flavour.md).
 *
 * ALL kubectl invocations in olam-cli MUST go through `kubectlWrap`.
 * Direct `child_process.spawn('kubectl', ...)` calls outside this module
 * break the timeout guarantee and fail `audit:kubectl-callers` (Seam C1).
 *
 * Timeout lifecycle:
 *   1. Spawn kubectl with the provided args.
 *   2. Start wall-clock timer (default 120 000 ms; per-call configurable).
 *   3. On timer fire: SIGTERM the process; wait 5 000 ms; SIGKILL if still alive.
 *   4. Returns { ok: false, reason: 'timeout', … } immediately after the kill
 *      sequence completes (does not wait further for exit after SIGKILL).
 *
 * Returns:
 *   { ok: true,  stdout, stderr, exitCode }       — clean exit 0
 *   { ok: false, stdout, stderr, exitCode, reason } — non-zero exit or timeout
 *
 * reason values:
 *   'timeout'  — wall-clock limit exceeded; process SIGKILLed
 *   'spawn'    — spawn itself failed (e.g. kubectl not on PATH)
 *   'nonzero'  — kubectl exited with non-zero status
 */
import { spawn } from 'node:child_process';
export const DEFAULT_TIMEOUT_MS = 120_000;
const SIGKILL_GRACE_MS = 5_000;
/**
 * Invoke kubectl with a timeout.
 *
 * @param args - kubectl arguments (e.g. ['get', 'pods', '-n', 'olam'])
 * @param opts - timeout, env overrides, and spawn factory for injection
 */
export async function kubectlWrap(args, opts = {}) {
    const timeoutMs = opts.timeout ?? DEFAULT_TIMEOUT_MS;
    const spawnImpl = opts.spawnImpl ?? spawn;
    const stdout = [];
    const stderr = [];
    return new Promise((resolve) => {
        let resolved = false;
        let killTimer = null;
        let sigkillTimer = null;
        function safeResolve(r) {
            if (resolved)
                return;
            resolved = true;
            if (killTimer !== null) {
                clearTimeout(killTimer);
                killTimer = null;
            }
            if (sigkillTimer !== null) {
                clearTimeout(sigkillTimer);
                sigkillTimer = null;
            }
            resolve(r);
        }
        let child;
        try {
            child = spawnImpl('kubectl', [...args], {
                stdio: [opts.stdin !== undefined ? 'pipe' : 'ignore', 'pipe', 'pipe'],
                env: { ...process.env, ...(opts.env ?? {}) },
            });
        }
        catch (err) {
            resolve({
                ok: false,
                stdout: '',
                stderr: err instanceof Error ? err.message : String(err),
                exitCode: -1,
                reason: 'spawn',
            });
            return;
        }
        // Write stdin payload and close the stream when provided.
        if (opts.stdin !== undefined && child.stdin) {
            child.stdin.write(opts.stdin, 'utf8');
            child.stdin.end();
        }
        if (child.stdout) {
            child.stdout.on('data', (chunk) => { stdout.push(chunk.toString('utf8')); });
        }
        if (child.stderr) {
            child.stderr.on('data', (chunk) => { stderr.push(chunk.toString('utf8')); });
        }
        child.on('error', (err) => {
            safeResolve({
                ok: false,
                stdout: stdout.join(''),
                stderr: err.message,
                exitCode: -1,
                reason: 'spawn',
            });
        });
        child.on('exit', (code) => {
            const exitCode = code ?? -1;
            const stdoutStr = stdout.join('');
            const stderrStr = stderr.join('');
            if (exitCode === 0) {
                safeResolve({ ok: true, stdout: stdoutStr, stderr: stderrStr, exitCode });
            }
            else {
                safeResolve({ ok: false, stdout: stdoutStr, stderr: stderrStr, exitCode, reason: 'nonzero' });
            }
        });
        // Wall-clock timeout: SIGTERM then SIGKILL after grace period.
        killTimer = setTimeout(() => {
            killTimer = null;
            // SIGTERM — polite shutdown request.
            try {
                child.kill('SIGTERM');
            }
            catch { /* already exited */ }
            // SIGKILL after 5 s grace — hard kill if SIGTERM wasn't enough.
            sigkillTimer = setTimeout(() => {
                sigkillTimer = null;
                try {
                    child.kill('SIGKILL');
                }
                catch { /* already exited */ }
                // Resolve immediately after SIGKILL without waiting for the 'exit' event.
                // The 'exit' event may fire after this resolve; safeResolve is idempotent.
                safeResolve({
                    ok: false,
                    stdout: stdout.join(''),
                    stderr: stderr.join(''),
                    exitCode: -1,
                    reason: 'timeout',
                });
            }, SIGKILL_GRACE_MS);
        }, timeoutMs);
    });
}
//# sourceMappingURL=kubectl-wrap.js.map