const SECRET_NAMES = [
    "ANTHROPIC_API_KEY",
    "VETRA_ANTHROPIC_API_KEY",
    "VETRA_CLI_ANTHROPIC_API_KEY",
];
const SECRET_RETRIES = 3;
async function withRetry(fn, retries, sleep) {
    let lastErr;
    for (let attempt = 0; attempt < retries; attempt++) {
        try {
            await fn();
            return;
        }
        catch (err) {
            lastErr = err;
            if (attempt < retries - 1)
                await sleep(2 ** attempt * 100);
        }
    }
    throw lastErr;
}
/**
 * Atomically claim one warm env for the caller `did` (a `did:pkh:...`), transfer
 * ownership (system SET_OWNER on the owner-null env), and inject the caller's
 * attached key. The key lookup is keyed by the full DID (matching how the
 * redemption was recorded); `claimedBy`/owner use the bare address. Returns null
 * when the caller has no key or the pool is empty (frontend then cold-falls-back).
 *
 * Self-healing: transient secret writes are retried; on ANY post-assign failure
 * the half-claimed env is TERMINATED (not left as an owned, key-less orphan) and
 * the keeper provisions a replacement, so a failed claim leaks nothing.
 */
export async function claimWarmEnvironment(d, did) {
    const addr = (did.split(":").pop() ?? did).toLowerCase();
    const sleep = d.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
    // 1. No key → don't consume an env.
    const apiKey = await d.getKeyForDid(did);
    if (!apiKey) {
        d.logger.info(`[studio-pool] claim: no attached key for ${addr}`);
        return null;
    }
    // 2. Atomic assign (only a READY, AVAILABLE, current-version env).
    const env = await d.claimDb.claimOneAvailable(addr, d.cfg.version, d.nowIso());
    if (!env || !env.tenantId) {
        d.logger.info(`[studio-pool] claim: pool empty for ${addr}`);
        return null;
    }
    try {
        // 3. Inject the key (under all required names) AND ADMINS in ONE batched
        //    write — a single pg_notify → one secrets-controller reconcile → ONE
        //    Reloader bounce of the agent pod. Delivering ADMINS here (instead of
        //    via an owner-change gitops re-render) is what collapses the claim from
        //    two restarts (~3s secret bounce + ~30s gitops/ADMINS rollout) to one
        //    ~3s bounce. Retried as a whole for transient failures.
        const secretEntries = [
            ...SECRET_NAMES.map((key) => ({ key, value: apiKey })),
            { key: "ADMINS", value: addr },
        ];
        await withRetry(() => d.setSecrets(env.tenantId, secretEntries), SECRET_RETRIES, sleep);
        // 4. Transfer ownership LAST (system action — env was created owner-null).
        //    SET_OWNER updates ownership/read-model only; it no longer triggers a
        //    gitops rollout (ADMINS now arrives via the Secret above).
        await d.setOwner(env.id, addr);
        d.logger.info(`[studio-pool] claimed ${env.id} (${env.tenantId}) for ${addr}`);
        return {
            documentId: env.id,
            subdomain: env.subdomain ?? "",
            tenantId: env.tenantId,
        };
    }
    catch (err) {
        // Half-claimed (owned, possibly key-less) → delete so nothing leaks; the
        // keeper refills. Caller gets null → cold-falls-back.
        d.logger.warn(`[studio-pool] claim post-assign failed for ${env.id}; deleting: ${String(err)}`);
        await d.deleteEnv(env.id).catch((e) => d.logger.warn(`[studio-pool] cleanup delete failed for ${env.id}: ${String(e)}`));
        return null;
    }
}
