"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ensureSupportedClient = void 0;
exports.setRequestLogContext = setRequestLogContext;
exports.buildFastifyLogger = buildFastifyLogger;
exports.buildFastifyServerOptions = buildFastifyServerOptions;
exports.registerStructuredRequestLogging = registerStructuredRequestLogging;
exports.readClientHeaders = readClientHeaders;
exports.formatClientError = formatClientError;
exports.buildClientGuard = buildClientGuard;
const node_crypto_1 = require("node:crypto");
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const node_stream_1 = require("node:stream");
const index_1 = require("../index");
const logging_1 = require("./logging");
function normalizeLogContextValue(value) {
    if (value === undefined || value === null) {
        return undefined;
    }
    const normalized = `${value}`.trim();
    return normalized.length > 0 ? normalized : undefined;
}
function setRequestLogContext(request, input) {
    const nextUserId = normalizeLogContextValue(input.userId);
    const nextRoomId = normalizeLogContextValue(input.roomId);
    const current = request.requestLogContext ?? {};
    request.requestLogContext = {
        ...(current.userId ? { userId: current.userId } : {}),
        ...(current.roomId ? { roomId: current.roomId } : {}),
        ...(nextUserId ? { userId: nextUserId } : {}),
        ...(nextRoomId ? { roomId: nextRoomId } : {}),
    };
}
function createStructuredLogMirrorStream(logFilePath) {
    node_fs_1.default.mkdirSync(node_path_1.default.dirname(logFilePath), { recursive: true });
    const fileStream = node_fs_1.default.createWriteStream(logFilePath, { flags: 'a' });
    return new node_stream_1.Writable({
        write(chunk, _encoding, callback) {
            process.stdout.write(chunk);
            fileStream.write(chunk, callback);
        },
        final(callback) {
            fileStream.end(callback);
        },
    });
}
function buildFastifyLogger(service) {
    const logger = {
        level: process.env['LOG_LEVEL']?.trim() || 'info',
        base: (0, index_1.resolveStructuredLogFields)(service),
        messageKey: 'message',
        formatters: {
            level(label) {
                return { level: label };
            },
        },
        timestamp: () => `,"timestamp":"${new Date().toISOString()}"`,
    };
    const logFilePath = (0, logging_1.resolveStructuredLogFilePath)(service);
    if (!logFilePath) {
        return logger;
    }
    return {
        ...logger,
        stream: createStructuredLogMirrorStream(logFilePath),
    };
}
function buildFastifyServerOptions(params) {
    return {
        routerOptions: {
            maxParamLength: 2048,
        },
        logger: buildFastifyLogger(params.service),
        disableRequestLogging: true,
        requestIdHeader: 'x-request-id',
        genReqId(request) {
            const explicit = request.headers['x-request-id'];
            if (typeof explicit === 'string' && explicit.trim().length > 0) {
                return explicit.trim();
            }
            if (Array.isArray(explicit) && typeof explicit[0] === 'string' && explicit[0].trim().length > 0) {
                return explicit[0].trim();
            }
            return (0, node_crypto_1.randomUUID)();
        },
        ...(typeof params.bodyLimit === 'number' ? { bodyLimit: params.bodyLimit } : {}),
        ...(params.trustProxy !== undefined ? { trustProxy: params.trustProxy } : {}),
    };
}
function registerStructuredRequestLogging(app) {
    app.addHook('onRequest', async (request, reply) => {
        reply.header('x-request-id', request.id);
        request.log.info({
            request_id: request.id,
            method: request.method,
            path: request.url,
            host: request.hostname,
            remote_ip: request.ip,
        }, 'request_started');
    });
    app.addHook('onError', async (request, _reply, error) => {
        const context = request.requestLogContext ?? {};
        request.log.error({
            err: error,
            request_id: request.id,
            method: request.method,
            path: request.url,
            user_id: context.userId,
            room_id: context.roomId,
        }, 'request_failed');
    });
    app.addHook('onResponse', async (request, reply) => {
        const context = request.requestLogContext ?? {};
        const elapsedMs = typeof reply.elapsedTime === 'number' ? Math.round(reply.elapsedTime) : undefined;
        request.log.info({
            request_id: request.id,
            method: request.method,
            path: request.url,
            route: request.routeOptions.url,
            status_code: reply.statusCode,
            duration_ms: elapsedMs,
            user_id: context.userId,
            room_id: context.roomId,
        }, 'request_completed');
    });
}
function readClientHeaders(request) {
    const headers = request.headers || {};
    const getHeader = (name) => {
        const value = headers[name] ?? headers[name.toLowerCase()];
        if (Array.isArray(value)) {
            return typeof value[0] === 'string' ? value[0] : undefined;
        }
        return typeof value === 'string' ? value : undefined;
    };
    return {
        client: getHeader(index_1.CLIENT_HEADER_NAMES.client) ?? '',
        clientVersion: getHeader(index_1.CLIENT_HEADER_NAMES.clientVersion) ?? '',
        clientBuild: getHeader(index_1.CLIENT_HEADER_NAMES.clientBuild) ?? '',
        platform: getHeader(index_1.CLIENT_HEADER_NAMES.platform) ?? '',
        platformVersion: getHeader(index_1.CLIENT_HEADER_NAMES.platformVersion) ?? ''
    };
}
function formatClientError(failure) {
    const meta = (0, index_1.loadClientMeta)();
    const missingHeaders = failure.missingHeaders ?? null;
    const provided = failure.ok ? null : failure.provided ?? null;
    const currentVersion = provided?.clientVersion ?? null;
    const currentBuild = provided?.clientBuild ?? null;
    const defaultMessage = (() => {
        switch (failure.code) {
            case 'missing_client_metadata': {
                if (missingHeaders && missingHeaders.length > 0) {
                    return `Unsupported request. Missing client headers: ${missingHeaders.join(', ')}.`;
                }
                return 'Unsupported request. Client metadata headers are required.';
            }
            case 'unsupported_client':
                return 'Unsupported client. Please update your application to continue.';
            case 'unsupported_client_version': {
                const currentLabel = currentVersion
                    ? `${currentVersion}${currentBuild !== null ? ` build ${currentBuild}` : ''}`
                    : currentBuild !== null
                        ? `build ${currentBuild}`
                        : 'unknown';
                const requiredVersion = failure.requiredVersion ?? currentVersion ?? 'latest';
                const requiredBuildLabel = failure.requiredBuild !== null && failure.requiredBuild !== undefined
                    ? ` build ${failure.requiredBuild}`
                    : '';
                return `Unsupported version. Please update the app. Current version ${currentLabel}. Requires version ${requiredVersion}${requiredBuildLabel}.`;
            }
            case 'unsupported_client_build': {
                const currentLabel = currentVersion
                    ? `${currentVersion}${currentBuild !== null ? ` build ${currentBuild}` : ''}`
                    : currentBuild !== null
                        ? `build ${currentBuild}`
                        : 'unknown';
                const requiredVersion = failure.requiredVersion ?? currentVersion ?? 'latest';
                const requiredBuildLabel = failure.requiredBuild !== null && failure.requiredBuild !== undefined
                    ? ` build ${failure.requiredBuild}`
                    : '';
                return `Unsupported version. Please update the app. Current version ${currentLabel}. Requires version ${requiredVersion}${requiredBuildLabel}.`;
            }
            case 'unsupported_platform_version': {
                const requirement = failure.requiredPlatformVersion
                    ? `version ${failure.requiredPlatformVersion} or later`
                    : 'a newer platform version';
                const runtime = provided
                    ? `${provided.platform} ${provided.platformVersion}`
                    : 'current platform unknown';
                return `Unsupported platform version. Requires ${requirement}. (${runtime})`;
            }
            default:
                return 'Unsupported client environment. Please update and try again.';
        }
    })();
    return {
        error: failure.code,
        message: failure.message || defaultMessage,
        requiredVersion: failure.requiredVersion ?? null,
        requiredBuild: failure.requiredBuild ?? null,
        requiredPlatformVersion: failure.requiredPlatformVersion ?? null,
        currentVersion,
        currentBuild,
        missingHeaders,
    };
}
function buildClientGuard(reader = readClientHeaders, validator = index_1.validateClientEnvironment, formatter = formatClientError) {
    return async function ensureSupportedClient(request, reply) {
        const runtimeInfo = reader(request);
        const validation = validator(runtimeInfo);
        if (!validation.ok) {
            const statusCode = validation.code === 'missing_client_metadata' ? 400 : 426;
            request.log.warn({ runtimeInfo }, `Rejected client: ${validation.code}`);
            await reply.code(statusCode).send(formatter(validation));
            return null;
        }
        request.clientRuntime = validation.normalized;
        return validation.normalized;
    };
}
exports.ensureSupportedClient = buildClientGuard();
