"use strict";
/**
 * SplunkRetentionAdapter -- P4-RET-04 Splunk HEC retention API (AC 14)
 *
 * Wired 2026-05-02 per Thomas Layer 0 directive (F-NEW-CRIT-31).
 *
 * Configuration:
 *   - SPLUNK_HEC_URL: Splunk HEC base URL (e.g. https://splunk.example.com:8088)
 *   - SPLUNK_HEC_TOKEN: HEC token with admin-level retention API access
 *   - SPLUNK_INDEX_MAP: JSON blob mapping tenantId -> Splunk index name
 *
 * Behavior:
 *   - Construction: succeeds with warning if no creds (so reconciliation can
 *     skip cleanly for tenants without a Splunk binding). Per-tenant call
 *     fail-fast if a tenant is bound but creds missing.
 *   - setTenantRetention(): POST /services/data/retentions
 *       { index, frozenTimePeriodInSecs: days * 86400 }
 *   - 10s default timeout, 1 retry on 5xx or network error.
 *   - On final failure, dispatches to DLQ callback (if injected) and rethrows.
 *
 * Spec: P4-RET-01 Section 2.4, AC 14
 *
 * @connexum/ai-governance
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.SplunkRetentionAdapter = void 0;
const ADAPTER_TAG = 'splunk-retention';
const DEFAULT_TIMEOUT_MS = 10_000;
class SplunkRetentionAdapter {
    aggregatorType = 'splunk';
    baseUrl;
    token;
    indexMap;
    timeoutMs;
    dlq;
    fetchImpl;
    constructor(opts) {
        this.baseUrl = opts?.baseUrl ?? process.env.SPLUNK_HEC_URL;
        this.token = opts?.token ?? process.env.SPLUNK_HEC_TOKEN;
        const rawMap = opts?.indexMap ??
            (process.env.SPLUNK_INDEX_MAP ? JSON.parse(process.env.SPLUNK_INDEX_MAP) : {});
        this.indexMap = new Map(Object.entries(rawMap));
        this.timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
        this.dlq = opts?.dlq;
        this.fetchImpl = opts?.fetchImpl ?? globalThis.fetch;
    }
    async setTenantRetention(tenantId, days) {
        if (!this.baseUrl || !this.token) {
            // Tenant has no Splunk binding (or operator has not configured creds).
            // Skip with warning so reconciliation continues for other tenants.
            console.warn(`[${ADAPTER_TAG}] no credentials configured. Tenant=${tenantId}, days=${days}. ` +
                `Set SPLUNK_HEC_URL + SPLUNK_HEC_TOKEN to enable.`);
            return;
        }
        const index = this.indexMap.get(tenantId);
        if (!index) {
            console.warn(`[${ADAPTER_TAG}] no index mapped for tenant=${tenantId}. ` +
                `Update SPLUNK_INDEX_MAP to enable.`);
            return;
        }
        const frozenTimePeriodInSecs = days * 86400;
        const url = `${this.baseUrl.replace(/\/$/, '')}/services/data/retentions`;
        const body = JSON.stringify({ index, frozenTimePeriodInSecs });
        const headers = {
            Authorization: `Splunk ${this.token}`,
            'Content-Type': 'application/json',
        };
        await this.dispatchWithRetry(tenantId, days, async () => {
            return this.doFetch(url, { method: 'POST', headers, body });
        });
    }
    async getTenantRetention(tenantId) {
        if (!this.baseUrl || !this.token)
            return null;
        const index = this.indexMap.get(tenantId);
        if (!index)
            return null;
        const url = `${this.baseUrl.replace(/\/$/, '')}/services/data/retentions/${encodeURIComponent(index)}`;
        try {
            const res = await this.doFetch(url, {
                method: 'GET',
                headers: { Authorization: `Splunk ${this.token}` },
            });
            if (!res.ok)
                return null;
            const json = (await res.json());
            if (typeof json.frozenTimePeriodInSecs !== 'number')
                return null;
            return Math.floor(json.frozenTimePeriodInSecs / 86400);
        }
        catch {
            return null;
        }
    }
    async doFetch(url, init) {
        const controller = new AbortController();
        const timer = setTimeout(() => controller.abort(), this.timeoutMs);
        try {
            return await this.fetchImpl(url, { ...init, signal: controller.signal });
        }
        finally {
            clearTimeout(timer);
        }
    }
    async dispatchWithRetry(tenantId, days, op) {
        let lastErr;
        for (let attempt = 1; attempt <= 2; attempt++) {
            try {
                const res = await op();
                if (res.ok)
                    return;
                if (res.status >= 500 && attempt === 1) {
                    lastErr = new Error(`Splunk retention API ${res.status} ${res.statusText}`);
                    continue;
                }
                throw new Error(`Splunk retention API failed: ${res.status} ${res.statusText}`);
            }
            catch (err) {
                lastErr = err;
                if (attempt === 1 && this.isRetryable(err))
                    continue;
                break;
            }
        }
        const errMsg = lastErr instanceof Error ? lastErr.message : String(lastErr);
        if (this.dlq) {
            this.dlq({
                adapterType: this.aggregatorType,
                tenantId,
                days,
                attempts: 2,
                error: errMsg,
                occurredAt: new Date().toISOString(),
            });
        }
        console.error(`[${ADAPTER_TAG}] retention push failed after retry. tenant=${tenantId} days=${days} err=${errMsg}`);
        throw lastErr instanceof Error ? lastErr : new Error(errMsg);
    }
    isRetryable(err) {
        if (!(err instanceof Error))
            return false;
        // Network errors and aborts are retryable once.
        return (err.name === 'AbortError' ||
            err.message.includes('ECONNREFUSED') ||
            err.message.includes('ECONNRESET') ||
            err.message.includes('ETIMEDOUT') ||
            err.message.includes('fetch failed'));
    }
}
exports.SplunkRetentionAdapter = SplunkRetentionAdapter;
//# sourceMappingURL=splunk.js.map