import { readFileSync } from "fs";
const SA_TOKEN_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/token";
const DEFAULT_KEY_NAME_PREFIX = "vetra-tenant-";
// Re-auth 5 min before the token TTL runs out so decrypts-in-flight don't race.
const TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000;
export class OpenBaoTransitClient {
    addr;
    role;
    keyNamePrefix;
    readToken;
    tokenCache = null;
    constructor(opts) {
        this.addr = opts.addr.replace(/\/$/, "");
        this.role = opts.role;
        this.keyNamePrefix = opts.keyNamePrefix ?? DEFAULT_KEY_NAME_PREFIX;
        this.readToken = opts.tokenReader ?? readFileSync;
    }
    keyFor(tenantId) {
        return `${this.keyNamePrefix}${tenantId}`;
    }
    async ensureTenantKey(tenantId) {
        const key = this.keyFor(tenantId);
        const token = await this.getToken();
        const res = await fetch(`${this.addr}/v1/transit/keys/${encodeURIComponent(key)}`, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "X-Vault-Token": token,
            },
            body: JSON.stringify({
                type: "aes256-gcm96",
                deletion_allowed: false,
            }),
        });
        // 204 = created; 400 with "existing key" also acceptable (idempotent).
        if (res.status === 204)
            return;
        const text = await res.text();
        if (res.status === 400 && /existing key/i.test(text))
            return;
        if (!res.ok) {
            throw new Error(`OpenBao ensureTenantKey(${tenantId}) failed (${res.status}): ${text}`);
        }
    }
    async encrypt(tenantId, plaintext) {
        const key = this.keyFor(tenantId);
        const token = await this.getToken();
        const res = await fetch(`${this.addr}/v1/transit/encrypt/${encodeURIComponent(key)}`, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "X-Vault-Token": token,
            },
            body: JSON.stringify({
                plaintext: Buffer.from(plaintext, "utf8").toString("base64"),
            }),
        });
        if (!res.ok) {
            const text = await res.text();
            throw new Error(`OpenBao transit encrypt(${tenantId}) failed (${res.status}): ${text}`);
        }
        const json = (await res.json());
        return json.data.ciphertext;
    }
    async decrypt(tenantId, ciphertext) {
        const key = this.keyFor(tenantId);
        const token = await this.getToken();
        const res = await fetch(`${this.addr}/v1/transit/decrypt/${encodeURIComponent(key)}`, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "X-Vault-Token": token,
            },
            body: JSON.stringify({ ciphertext }),
        });
        if (!res.ok) {
            const text = await res.text();
            throw new Error(`OpenBao transit decrypt(${tenantId}) failed (${res.status}): ${text}`);
        }
        const json = (await res.json());
        return Buffer.from(json.data.plaintext, "base64").toString("utf8");
    }
    async authenticate() {
        const saToken = this.readToken(SA_TOKEN_PATH, "utf8").trim();
        const res = await fetch(`${this.addr}/v1/auth/kubernetes/login`, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ jwt: saToken, role: this.role }),
        });
        if (!res.ok) {
            const text = await res.text();
            throw new Error(`OpenBao authentication failed (${res.status}): ${text}`);
        }
        const json = (await res.json());
        const ttlMs = Math.max(json.auth.lease_duration, 60) * 1000;
        this.tokenCache = {
            token: json.auth.client_token,
            expiresAt: Date.now() + ttlMs - TOKEN_EXPIRY_BUFFER_MS,
        };
        return json.auth.client_token;
    }
    async getToken() {
        if (this.tokenCache && this.tokenCache.expiresAt > Date.now()) {
            return this.tokenCache.token;
        }
        return this.authenticate();
    }
}
