import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
import YAML from 'yaml';
function readWebhookSecret(squadsDir, squadName) {
    try {
        const yamlPath = path.join(squadsDir, squadName, 'squad.yaml');
        if (!fs.existsSync(yamlPath))
            return undefined;
        const parsed = YAML.parse(fs.readFileSync(yamlPath, 'utf-8'));
        return parsed?.squad?.webhooks?.secret;
    }
    catch {
        return undefined;
    }
}
// Deduplication — prevents double-spawn when callers retry on slow 202 responses
const DEDUP_WINDOW_MS = 30_000;
const pendingRequests = new Map();
function deduplicationHash(squadName, body) {
    // Normalize body: remove timestamp-like fields that vary between retries
    const clean = {};
    const raw = (body ?? {});
    for (const [k, v] of Object.entries(raw)) {
        if (/timestamp|time|date|_at$/i.test(k))
            continue;
        clean[k] = v;
    }
    return crypto
        .createHash('sha256')
        .update(`${squadName}:${JSON.stringify(clean)}`)
        .digest('hex')
        .slice(0, 16);
}
function pruneExpired() {
    const now = Date.now();
    for (const [hash, entry] of pendingRequests) {
        if (now - entry.triggeredAt > DEDUP_WINDOW_MS) {
            pendingRequests.delete(hash);
        }
    }
}
export async function webhookRoutes(app, opts) {
    const { jobRunner, squadsDir } = opts;
    app.post('/api/webhooks/:squadName', async (request, reply) => {
        const { squadName } = request.params;
        if (!fs.existsSync(path.join(squadsDir, squadName, 'squad.yaml'))) {
            return reply.code(404).send({ error: `Squad "${squadName}" not found` });
        }
        const configuredSecret = readWebhookSecret(squadsDir, squadName);
        if (configuredSecret) {
            const provided = request.headers['x-webhook-secret'];
            if (provided !== configuredSecret) {
                return reply.code(401).send({ error: 'Invalid or missing webhook secret' });
            }
        }
        // Deduplication: return 202 immediately if an identical request is in-flight
        pruneExpired();
        const hash = deduplicationHash(squadName, request.body);
        if (pendingRequests.has(hash)) {
            return reply.code(202).send({ status: 'already_triggered', squadName, deduplicated: true });
        }
        pendingRequests.set(hash, { squadName, triggeredAt: Date.now() });
        // Auto-remove after the dedup window
        setTimeout(() => pendingRequests.delete(hash), DEDUP_WINDOW_MS);
        const prompt = request.body?.prompt ?? `Execute the "${squadName}" squad pipeline`;
        // Fire and forget — don't await
        jobRunner.run({
            squadName,
            prompt,
            timeoutMinutes: 60,
            retryOnFailure: 0,
            attempt: 1,
        }).catch((err) => {
            console.error(`[webhook] Squad "${squadName}" run failed:`, err.message);
        });
        return reply.code(202).send({ status: 'triggered', squadName });
    });
}
//# sourceMappingURL=webhook-routes.js.map