/**
 * SentinelRetentionAdapter -- P4-RET-04 Azure Sentinel retention (AC 14)
 *
 * Wired 2026-05-02 per Thomas Layer 0 directive (F-NEW-CRIT-31).
 *
 * Configuration:
 *   - AZURE_TENANT_ID: Azure AD tenant ID
 *   - AZURE_CLIENT_ID: App registration client ID with Log Analytics Contributor
 *   - AZURE_CLIENT_SECRET: Client secret
 *   - AZURE_SUBSCRIPTION_ID: Azure subscription containing the workspaces
 *   - SENTINEL_WORKSPACE_MAP: JSON blob mapping tenantId ->
 *       { resourceGroupName, workspaceName }
 *   - opts.armBaseUrl / opts.oauthBaseUrl (test overrides)
 *
 * Behavior:
 *   1. Acquire OAuth2 token via client_credentials flow against
 *      login.microsoftonline.com/<tenantId>/oauth2/v2.0/token
 *   2. PATCH workspace at management.azure.com/.../workspaces/<ws>?api-version=2022-10-01
 *      { properties: { retentionInDays: <days> } }
 *   - 10s default timeout per call, 1 retry on 5xx or network error.
 *   - Token cached in-process for ~50 minutes (default Azure lifetime is 60 min).
 *   - On final failure, dispatches to DLQ callback (if injected) and rethrows.
 *
 * Spec: P4-RET-01 Section 2.4, AC 14
 *
 * @connexum/ai-governance
 */
const ADAPTER_TAG = 'sentinel-retention';
const DEFAULT_TIMEOUT_MS = 10_000;
const TOKEN_TTL_MS = 50 * 60 * 1000;
const ARM_DEFAULT = 'https://management.azure.com';
const OAUTH_DEFAULT = 'https://login.microsoftonline.com';
export class SentinelRetentionAdapter {
    aggregatorType = 'sentinel';
    tenantId;
    clientId;
    clientSecret;
    subscriptionId;
    workspaceMap;
    armBaseUrl;
    oauthBaseUrl;
    timeoutMs;
    dlq;
    fetchImpl;
    cachedToken = null;
    constructor(opts) {
        this.tenantId = opts?.tenantId ?? process.env.AZURE_TENANT_ID;
        this.clientId = opts?.clientId ?? process.env.AZURE_CLIENT_ID;
        this.clientSecret = opts?.clientSecret ?? process.env.AZURE_CLIENT_SECRET;
        this.subscriptionId = opts?.subscriptionId ?? process.env.AZURE_SUBSCRIPTION_ID;
        const rawMap = opts?.workspaceMap ??
            (process.env.SENTINEL_WORKSPACE_MAP
                ? JSON.parse(process.env.SENTINEL_WORKSPACE_MAP)
                : {});
        this.workspaceMap = new Map(Object.entries(rawMap));
        this.armBaseUrl = (opts?.armBaseUrl ?? ARM_DEFAULT).replace(/\/$/, '');
        this.oauthBaseUrl = (opts?.oauthBaseUrl ?? OAUTH_DEFAULT).replace(/\/$/, '');
        this.timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
        this.dlq = opts?.dlq;
        this.fetchImpl = opts?.fetchImpl ?? globalThis.fetch;
    }
    async setTenantRetention(tenantId, days) {
        if (!this.tenantId ||
            !this.clientId ||
            !this.clientSecret ||
            !this.subscriptionId) {
            console.warn(`[${ADAPTER_TAG}] no Azure credentials. Tenant=${tenantId}, days=${days}. ` +
                `Set AZURE_TENANT_ID + AZURE_CLIENT_ID + AZURE_CLIENT_SECRET + AZURE_SUBSCRIPTION_ID to enable.`);
            return;
        }
        const workspace = this.workspaceMap.get(tenantId);
        if (!workspace) {
            console.warn(`[${ADAPTER_TAG}] no workspace mapped for tenant=${tenantId}. ` +
                `Update SENTINEL_WORKSPACE_MAP to enable.`);
            return;
        }
        await this.dispatchWithRetry(tenantId, days, async () => {
            const token = await this.getAccessToken();
            const url = `${this.armBaseUrl}/subscriptions/${encodeURIComponent(this.subscriptionId)}` +
                `/resourceGroups/${encodeURIComponent(workspace.resourceGroupName)}` +
                `/providers/Microsoft.OperationalInsights/workspaces/${encodeURIComponent(workspace.workspaceName)}` +
                `?api-version=2022-10-01`;
            const body = JSON.stringify({ properties: { retentionInDays: days } });
            return this.doFetch(url, {
                method: 'PATCH',
                headers: {
                    Authorization: `Bearer ${token}`,
                    'Content-Type': 'application/json',
                },
                body,
            });
        });
    }
    async getTenantRetention(tenantId) {
        if (!this.tenantId ||
            !this.clientId ||
            !this.clientSecret ||
            !this.subscriptionId) {
            return null;
        }
        const workspace = this.workspaceMap.get(tenantId);
        if (!workspace)
            return null;
        try {
            const token = await this.getAccessToken();
            const url = `${this.armBaseUrl}/subscriptions/${encodeURIComponent(this.subscriptionId)}` +
                `/resourceGroups/${encodeURIComponent(workspace.resourceGroupName)}` +
                `/providers/Microsoft.OperationalInsights/workspaces/${encodeURIComponent(workspace.workspaceName)}` +
                `?api-version=2022-10-01`;
            const res = await this.doFetch(url, {
                method: 'GET',
                headers: { Authorization: `Bearer ${token}` },
            });
            if (!res.ok)
                return null;
            const json = (await res.json());
            const v = json.properties?.retentionInDays;
            return typeof v === 'number' ? v : null;
        }
        catch {
            return null;
        }
    }
    /**
     * Fetch a bearer token from Azure AD via client_credentials grant.
     * Caches the token for TOKEN_TTL_MS.
     * Exposed as protected so subclasses / tests can override if needed.
     */
    async getAccessToken() {
        const now = Date.now();
        if (this.cachedToken && this.cachedToken.expiresAt > now + 60_000) {
            return this.cachedToken.token;
        }
        const url = `${this.oauthBaseUrl}/${encodeURIComponent(this.tenantId)}/oauth2/v2.0/token`;
        const form = new URLSearchParams({
            grant_type: 'client_credentials',
            client_id: this.clientId,
            client_secret: this.clientSecret,
            scope: 'https://management.azure.com/.default',
        });
        const res = await this.doFetch(url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: form.toString(),
        });
        if (!res.ok) {
            throw new Error(`Azure AD token endpoint failed: ${res.status} ${res.statusText}`);
        }
        const json = (await res.json());
        if (!json.access_token) {
            throw new Error('Azure AD token endpoint returned no access_token');
        }
        const lifetimeMs = typeof json.expires_in === 'number'
            ? Math.min(json.expires_in * 1000, TOKEN_TTL_MS)
            : TOKEN_TTL_MS;
        this.cachedToken = {
            token: json.access_token,
            expiresAt: now + lifetimeMs,
        };
        return json.access_token;
    }
    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(`Sentinel retention API ${res.status} ${res.statusText}`);
                    continue;
                }
                throw new Error(`Sentinel 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;
        return (err.name === 'AbortError' ||
            err.message.includes('ECONNREFUSED') ||
            err.message.includes('ECONNRESET') ||
            err.message.includes('ETIMEDOUT') ||
            err.message.includes('fetch failed'));
    }
}
//# sourceMappingURL=sentinel.js.map