import { requireAllowedOrigin } from './authRoutes.js';
const maxAcceptedEvents = 150;
const maxAcceptedDataKeys = 24;
const maxDiagnosticValueLength = 160;
const diagnosticsBodyLimitBytes = 64 * 1024;
export async function registerClientDiagnosticsRoutes(app, config, services) {
    app.post('/api/client-diagnostics', {
        bodyLimit: diagnosticsBodyLimitBytes,
        preHandler: (request, reply) => requireClientDiagnosticsAuth(config, services, request, reply)
    }, async (request) => {
        const reason = sanitizeText(request.body?.reason, 80) || 'unknown';
        const events = normalizeEvents(request.body?.events);
        request.log.warn({
            event: 'client.diagnostics',
            reason,
            count: events.length,
            events
        }, 'client diagnostics received');
        return { ok: true, accepted: events.length };
    });
}
async function requireClientDiagnosticsAuth(config, services, request, reply) {
    await requireAllowedOrigin(config, request, reply);
    if (reply.sent) {
        return;
    }
    const session = await services.authService.requireSession(request);
    if (!session) {
        await reply.code(401).send({ error: 'unauthorized' });
    }
}
function normalizeEvents(value) {
    if (!Array.isArray(value)) {
        return [];
    }
    return value
        .slice(-maxAcceptedEvents)
        .map((event) => normalizeEvent(event))
        .filter((event) => event !== null);
}
function normalizeEvent(value) {
    if (!value || typeof value !== 'object') {
        return null;
    }
    const candidate = value;
    if (typeof candidate.at !== 'string' || typeof candidate.event !== 'string') {
        return null;
    }
    return {
        at: sanitizeText(candidate.at, 40),
        event: sanitizeText(candidate.event, 80),
        ...(candidate.data && typeof candidate.data === 'object' ? { data: normalizeData(candidate.data) } : {})
    };
}
function normalizeData(value) {
    const output = {};
    for (const [rawKey, rawValue] of Object.entries(value).slice(0, maxAcceptedDataKeys)) {
        const key = sanitizeText(rawKey, 64);
        if (!key || isSensitiveDiagnosticKey(key)) {
            continue;
        }
        const sanitizedValue = normalizeValue(rawValue);
        if (sanitizedValue !== undefined) {
            output[key] = sanitizedValue;
        }
    }
    return output;
}
function isSensitiveDiagnosticKey(key) {
    return /(?:password|secret|token|cookie|authorization|sessionid|session_id|nterminal_session|nterminal_device)/i.test(key);
}
function normalizeValue(value) {
    if (typeof value === 'string') {
        return sanitizeText(value, maxDiagnosticValueLength);
    }
    if (typeof value === 'number') {
        return Number.isFinite(value) ? value : null;
    }
    if (typeof value === 'boolean' || value === null) {
        return value;
    }
    return undefined;
}
function sanitizeText(value, maxLength) {
    return typeof value === 'string'
        ? redactSensitiveText(value).replace(/[\r\n\t]+/g, ' ').replace(/[^\x20-\x7E]/g, '_').slice(0, maxLength)
        : '';
}
function redactSensitiveText(value) {
    return value
        .replace(/https?:\/\/[^\s"'<>]+/gi, '[url]')
        .replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, 'Bearer [redacted]')
        .replace(/\b[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g, '[redacted]')
        .replace(/\b((?:auth|authorization|cookie|password|secret|session|sessionid|session_id|token)[a-z0-9_.:-]*\s*[:=]\s*)[^\s&,;]+/gi, '$1[redacted]')
        .replace(/\b(nterminal_session|nterminal_device)\s*=\s*[^\s;,]+/gi, '$1=[redacted]')
        .replace(/\b[A-Za-z0-9+/_=-]{48,}\b/g, '[redacted]');
}
//# sourceMappingURL=clientDiagnosticsRoutes.js.map