// engine/webhook/server.ts — Lightweight HTTP webhook receiver using node:http.
// No framework dependencies — only built-in Node.js modules.
import { createServer } from 'node:http';
import { randomUUID } from 'node:crypto';
import { validateSignature, extractSignatureHeader } from './signature.js';
import { dispatchWebhookEvent } from './event-handlers.js';
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const DEFAULT_PORT = 2358;
const MAX_RECENT_EVENTS = 20;
const MAX_BODY_SIZE = 1024 * 1024; // 1 MB
// ---------------------------------------------------------------------------
// Module state
// ---------------------------------------------------------------------------
let activeServer = null;
let serverState = makeIdleState();
let webhookSecret = '';
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/** Returns the current webhook server state (immutable snapshot). */
export function getWebhookState() {
    return serverState;
}
/** Replaces the webhook secret used for HMAC-SHA256 validation. */
export function setWebhookSecret(secret) {
    webhookSecret = secret;
}
/**
 * Starts the HTTP webhook server on the given port.
 * Idempotent: if already running on the same port, returns the current state.
 *
 * @throws if the port is in use or an OS-level error occurs
 */
export async function startWebhookServer(port = DEFAULT_PORT) {
    if (activeServer !== null) {
        return serverState;
    }
    return new Promise((resolve, reject) => {
        const server = createServer(handleRequest);
        server.on('error', (err) => {
            reject(err);
        });
        server.listen(port, () => {
            activeServer = server;
            serverState = {
                running: true,
                port,
                startedAt: new Date().toISOString(),
                eventsProcessed: 0,
                recentEvents: [],
            };
            resolve(serverState);
        });
    });
}
/**
 * Stops the running webhook server gracefully.
 * Idempotent: if already stopped, returns idle state.
 */
export async function stopWebhookServer() {
    if (activeServer === null) {
        serverState = makeIdleState();
        return serverState;
    }
    const server = activeServer;
    return new Promise((resolve, reject) => {
        server.close((err) => {
            /* v8 ignore start */
            if (err) {
                reject(err);
                return;
            }
            /* v8 ignore stop */
            activeServer = null;
            serverState = makeIdleState();
            resolve(serverState);
        });
    });
}
// ---------------------------------------------------------------------------
// HTTP request handler
// ---------------------------------------------------------------------------
function handleRequest(req, res) {
    if (req.method !== 'POST') {
        res.writeHead(405, { 'Content-Type': 'text/plain' });
        res.end('Method Not Allowed');
        return;
    }
    collectBody(req)
        .then((rawBody) => {
        processWebhookRequest(rawBody, req.headers, res);
    })
        .catch(() => {
        res.writeHead(400, { 'Content-Type': 'text/plain' });
        res.end('Bad Request');
    });
}
function collectBody(req) {
    return new Promise((resolve, reject) => {
        const chunks = [];
        let totalSize = 0;
        req.on('data', (chunk) => {
            totalSize += chunk.length;
            if (totalSize > MAX_BODY_SIZE) {
                reject(new Error('Payload too large'));
                return;
            }
            chunks.push(chunk);
        });
        req.on('end', () => {
            resolve(Buffer.concat(chunks));
        });
        req.on('error', reject);
    });
}
function processWebhookRequest(rawBody, headers, res) {
    // Signature validation (only if a secret is configured)
    if (webhookSecret) {
        const sig = extractSignatureHeader(headers);
        if (!validateSignature(rawBody, sig, webhookSecret)) {
            recordEvent('signature_invalid', false, 'Signature validation failed', '');
            res.writeHead(401, { 'Content-Type': 'text/plain' });
            res.end('Unauthorized');
            return;
        }
    }
    // Parse JSON body
    let payload;
    try {
        payload = JSON.parse(rawBody.toString('utf8'));
    }
    catch {
        res.writeHead(400, { 'Content-Type': 'text/plain' });
        res.end('Invalid JSON');
        return;
    }
    // Dispatch event
    const eventType = payload.meta.event_name;
    try {
        const result = dispatchWebhookEvent(payload);
        recordEvent(eventType, result.success, result.message, result.eventType);
        res.writeHead(result.statusCode, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ ok: true, message: result.message }));
        /* v8 ignore start */
    }
    catch (err) {
        const errMsg = err instanceof Error ? err.message : 'Unknown error';
        recordEvent(eventType, false, errMsg, eventType);
        res.writeHead(500, { 'Content-Type': 'text/plain' });
        res.end('Internal Server Error');
    }
    /* v8 ignore stop */
}
// ---------------------------------------------------------------------------
// Event recording (internal state update)
// ---------------------------------------------------------------------------
function recordEvent(_eventType, processed, summary, resolvedType) {
    const record = {
        id: randomUUID(),
        eventType: resolvedType,
        receivedAt: new Date().toISOString(),
        processed,
        summary,
        ...(processed ? {} : { error: summary }),
    };
    const recentEvents = [record, ...serverState.recentEvents].slice(0, MAX_RECENT_EVENTS);
    serverState = {
        ...serverState,
        eventsProcessed: serverState.eventsProcessed + 1,
        recentEvents,
    };
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function makeIdleState() {
    return {
        running: false,
        port: DEFAULT_PORT,
        startedAt: null,
        eventsProcessed: 0,
        recentEvents: [],
    };
}
//# sourceMappingURL=server.js.map