/**
 * pipeline/callback-delivery.ts
 *
 * Delivers evaluation results to a callback URL with HMAC signature.
 * Used by the API-triggered pipeline flow: after the report is published
 * to the Content Lake (system of record), the result is also POSTed
 * to the caller's callback URL.
 *
 * Fire-and-forget with 3 retries and exponential backoff. If callback
 * fails, log a warning — the result is still in the Content Lake.
 *
 * @see docs/design-docs/api-service-gateway.md
 */
import { createHmac } from "crypto";
import { assertSafeEgressUrl, EgressBlockedError, } from "../notifications/egress-guard.js";
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const MAX_RETRIES = 3;
const BASE_DELAY_MS = 1000;
const CALLBACK_TIMEOUT_MS = 10_000;
// ---------------------------------------------------------------------------
// HMAC Signature
// ---------------------------------------------------------------------------
/**
 * Generate an HMAC-SHA256 signature for the callback payload.
 *
 * The signature is computed over the raw JSON body using the callback URL
 * as the signing key. This allows the receiver to verify the request
 * originated from AILF without requiring a pre-shared secret.
 *
 * The receiver should:
 * 1. Read the raw request body
 * 2. Compute HMAC-SHA256(body, callbackUrl)
 * 3. Compare with the X-AILF-Signature header
 */
export function computeSignature(body, signingKey) {
    return createHmac("sha256", signingKey).update(body).digest("hex");
}
// ---------------------------------------------------------------------------
// Delivery
// ---------------------------------------------------------------------------
/**
 * Deliver a signed webhook envelope behind the egress guard. The body is signed
 * with `opts.signingSecret` (the global AILF secret), not the URL. Never throws:
 * an egress rejection or transport error returns `{ ok: false }`.
 *
 * Receivers verify by recomputing `HMAC-SHA256(raw body, sharedSecret)` and
 * comparing to the `X-AILF-Signature` header.
 */
export async function deliverWebhook(target, envelope, opts) {
    try {
        await assertSafeEgressUrl(target.url, { resolve: opts.resolve });
    }
    catch (err) {
        const error = err instanceof EgressBlockedError ? err.message : String(err);
        return { ok: false, error, attempts: 0 };
    }
    const body = JSON.stringify(envelope);
    const signature = computeSignature(body, opts.signingSecret);
    let lastError;
    let attempts = 0;
    for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
        attempts = attempt + 1;
        const controller = new AbortController();
        const timeout = setTimeout(() => controller.abort(), CALLBACK_TIMEOUT_MS);
        try {
            const response = await fetch(target.url, {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                    "X-AILF-Signature": signature,
                    ...(target.headers ?? {}),
                },
                body,
                signal: controller.signal,
            });
            if (response.ok)
                return { ok: true, attempts };
            lastError = `HTTP ${response.status}: ${response.statusText}`;
        }
        catch (err) {
            lastError = err instanceof Error ? err.message : String(err);
        }
        finally {
            clearTimeout(timeout);
        }
        if (attempt < MAX_RETRIES) {
            await new Promise((resolve) => setTimeout(resolve, BASE_DELAY_MS * Math.pow(2, attempt)));
        }
    }
    return { ok: false, error: lastError, attempts };
}
