/**
 * k8s-bootstrap.ts — apply the `olam` namespace + RBAC + secret set on a
 * fresh kubernetes cluster.
 *
 * B4 of olam-issue-npm-only-k3s. Without this an npm-only operator running
 * `olam upgrade --substrate=kubernetes` against a fresh cluster fails with:
 *
 *   Error from server (NotFound): namespaces "olam" not found
 *   Secret "olam-host-cp-secret" not found in namespace "olam"
 *
 * The 8-step upgrade flow (upgrade-kubernetes.ts) already runs
 * `seedManifestsFromBundle` to put manifest YAML at ~/.olam/k8s/manifests/,
 * but no one APPLIES them on first install — operators were expected to
 * `kubectl apply -f` by hand. That barrier is the npm-only blocker this
 * module closes.
 *
 * Idempotency: kubectl apply is idempotent by design for manifests
 * (server-side merge keys deduplicate). For secrets we render once and
 * persist the values to ~/.olam/k8s-secrets-state.json — subsequent runs
 * reuse the same values so worlds that have cached tokens don't break.
 *
 * Order (matches the seed manifest filename numeric prefix):
 *   1. 00-namespace.yaml        — host-cp namespace
 *   2. 10-serviceaccount.yaml   — host-cp ServiceAccount
 *   3. 20-rbac.yaml             — host-cp Role/RoleBinding
 *   4. 30-configmap.yaml        — host-cp env ConfigMap
 *   5. 45-pvc.yaml              — host-cp PersistentVolumeClaim
 *   6. host-cp Secret           — RENDERED from templates/40-secret-template.yaml
 *   7. per-peripheral manifests + secrets (auth-service, mcp-auth-service, kg-service, memory-service)
 *
 * Deployments + Services (50-deployment.yaml, 60-service.yaml) are NOT
 * applied here — that is upgrade-kubernetes.ts's job (it manages rollout
 * status + port-forward + audit log). ensureK8sBootstrap returns once the
 * "prerequisites" (namespace + RBAC + secrets) are in place.
 */
import { spawnSync } from 'node:child_process';
import { existsSync, readdirSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { kubectlWrap } from './kubectl-wrap.js';
import { installRoot } from '../install-root.js';
import { OLAM_HOME } from './config.js';
import { renderAllSecrets, SECRET_TEMPLATE_BINDINGS, } from './k8s-secret-render.js';
export const K8S_NAMESPACE = 'olam';
/**
 * Manifest files inside `<install-root>/host-cp/k8s/manifests/` that
 * ensureK8sBootstrap applies. Ordered numerically by filename prefix.
 * Deployment + Service are intentionally absent — upgrade-kubernetes.ts
 * applies those alongside its rollout-status step.
 */
const HOST_CP_PREREQ_MANIFESTS = [
    '00-namespace.yaml',
    '10-serviceaccount.yaml',
    '20-rbac.yaml',
    '30-configmap.yaml',
    '45-pvc.yaml',
];
/**
 * Peripheral subdirs under `<install-root>/host-cp/k8s/manifests/`. Each
 * carries its own 10/20/30/45 prerequisite YAMLs. 50-deployment + 60-service
 * are skipped here (upgrade.ts owns rollout).
 */
const PERIPHERAL_SUBDIRS = ['auth-service', 'mcp-auth-service', 'kg-service', 'memory-service'];
const PERIPHERAL_PREREQ_FILES = ['10-serviceaccount.yaml', '20-rbac.yaml', '30-configmap.yaml', '45-pvc.yaml'];
/**
 * kubectl apply wrapper — accepts a YAML string and applies via stdin (D20
 * stdin-safe; values never inlined into argv). When `manifestPath` is given
 * the file is applied via `-f <path>` instead (manifests stay on disk).
 */
async function kubectlApply(context, source, deps) {
    const wrap = deps.kubectlWrapImpl ?? kubectlWrap;
    if ('path' in source) {
        const r = await wrap(['--context', context, 'apply', '-f', source.path], { timeout: 30_000 });
        return { ok: r.ok, stderr: r.stderr };
    }
    const r = await wrap(['--context', context, 'apply', '-f', '-'], { timeout: 30_000, stdin: source.stdinYaml });
    return { ok: r.ok, stderr: r.stderr };
}
/**
 * Locate the bundled k8s assets directory. In install-mode this is
 * `<install-root>/host-cp/k8s/`. In dev-mode it falls back to the
 * monorepo's `packages/host-cp/k8s/`.
 */
export function resolveK8sAssetsRoot(installRootDir = installRoot()) {
    const installed = join(installRootDir, 'host-cp', 'k8s');
    if (existsSync(installed))
        return installed;
    // Dev-mode fallback. installRoot points at <repo>/packages/cli; walk up.
    const repoRoot = join(installRootDir, '..', '..');
    const monorepo = join(repoRoot, 'packages', 'host-cp', 'k8s');
    if (existsSync(monorepo))
        return monorepo;
    return null;
}
/**
 * Run the bootstrap. Idempotent on re-run.
 *
 * Apply order (numeric filename prefix mirrors apply ORDER):
 *   1. <root>/manifests/00-namespace.yaml + 10/20/30/45 (host-cp prereqs)
 *   2. Per-peripheral 10/20/30/45 prereqs (auth-service / mcp-auth-service / kg-service / memory-service)
 *   3. host-cp Secret (rendered from templates/40-secret-template.yaml)
 *   4. Per-peripheral Secrets (rendered from each *-secret-template.yaml)
 *
 * On the first failed apply we surface the kubectl stderr and stop —
 * partial-apply state is left in the cluster (kubectl apply is idempotent;
 * re-running picks up where we stopped).
 */
export async function ensureK8sBootstrap(opts, deps = {}) {
    const stdout = deps.stdout ?? process.stdout;
    const stderr = deps.stderr ?? process.stderr;
    const namespace = opts.namespace ?? K8S_NAMESPACE;
    const apply = deps.applyImpl ?? kubectlApply;
    const assetsRoot = deps.k8sAssetsRoot ?? resolveK8sAssetsRoot();
    if (assetsRoot === null || !existsSync(assetsRoot)) {
        return {
            exitCode: 1,
            result: { applied: [], skipped: [], secretResults: [] },
            error: 'Could not find bundled k8s assets. Expected <install-root>/host-cp/k8s/ or <repo>/packages/host-cp/k8s/. ' +
                'Reinstall the CLI: `npm install -g @pleri/olam-cli@latest`.',
        };
    }
    const manifestsRoot = join(assetsRoot, 'manifests');
    const applied = [];
    const skipped = [];
    // 1+2. host-cp prereq manifests + per-peripheral prereqs.
    const manifestPaths = [];
    for (const f of HOST_CP_PREREQ_MANIFESTS) {
        manifestPaths.push(join(manifestsRoot, f));
    }
    for (const sub of PERIPHERAL_SUBDIRS) {
        for (const f of PERIPHERAL_PREREQ_FILES) {
            const p = join(manifestsRoot, sub, f);
            if (existsSync(p))
                manifestPaths.push(p);
        }
    }
    for (const mp of manifestPaths) {
        if (opts.dryRun) {
            applied.push({ kind: 'manifest', path: mp });
            continue;
        }
        if (!existsSync(mp)) {
            skipped.push({ kind: 'manifest', ref: mp, reason: 'file not found in bundle (older package?)' });
            continue;
        }
        stdout.write(`  → applying ${mp.replace(assetsRoot, '<k8s-assets>')}\n`);
        const r = await apply(opts.context, { path: mp }, deps);
        if (!r.ok) {
            stderr.write(`error: kubectl apply -f ${mp} failed:\n${r.stderr}\n`);
            return {
                exitCode: 1,
                result: { applied, skipped, secretResults: [] },
                error: `kubectl apply of ${mp} failed`,
            };
        }
        applied.push({ kind: 'manifest', path: mp });
    }
    // 3+4. Render + apply secrets.
    const { results } = renderAllSecrets(assetsRoot, { rotate: opts.rotateSecrets === true, context: opts.context, namespace }, deps.renderDeps ?? {});
    for (const r of results) {
        if (r.status === 'skipped') {
            const reason = `unresolved source(s): ${r.missingSources.join('; ')}`;
            skipped.push({ kind: 'secret', ref: r.secretName, reason });
            stderr.write(`warn: Secret ${r.secretName} skipped — ${reason}\n`);
            stderr.write(`      Once you have the source value, apply manually:\n`);
            stderr.write(`        kubectl --context ${opts.context} apply -n ${namespace} -f - <<EOF\n        (rendered secret YAML)\nEOF\n`);
            continue;
        }
        if (opts.dryRun) {
            applied.push({ kind: 'secret', name: r.secretName });
            continue;
        }
        stdout.write(`  → applying Secret ${r.secretName}\n`);
        const a = await apply(opts.context, { stdinYaml: r.renderedYaml }, deps);
        if (!a.ok) {
            stderr.write(`error: kubectl apply Secret ${r.secretName} failed:\n${a.stderr}\n`);
            return {
                exitCode: 1,
                result: { applied, skipped, secretResults: results },
                error: `kubectl apply Secret ${r.secretName} failed`,
            };
        }
        applied.push({ kind: 'secret', name: r.secretName });
    }
    return { exitCode: 0, result: { applied, skipped, secretResults: results } };
}
/**
 * Read the list of expected Secret names — used by probeK8sRequiredSecrets
 * so the doctor probe stays in lockstep with the bootstrap module.
 */
export function expectedSecretNames() {
    return SECRET_TEMPLATE_BINDINGS.map((b) => b.secretName);
}
//# sourceMappingURL=k8s-bootstrap.js.map