import { Env } from '../config/env.js';
import { Logger } from '../config/logger.js';
import { respondWithMiddlewareFailure, } from './MiddlewareFailureResponder.js';
import { Xss } from '../security/Xss.js';
import { Validator } from '../validation/Validator.js';
const toRecord = (value) => {
    if (typeof value !== 'object' || value === null)
        return {};
    if (Array.isArray(value))
        return {};
    return value;
};
const applyFieldSanitizers = (input, sanitizers) => {
    if (sanitizers === undefined)
        return input;
    if (typeof input !== 'object' || input === null)
        return input;
    if (Array.isArray(input))
        return input;
    const record = input;
    let changed = false;
    const out = { ...record };
    for (const [field, sanitizer] of Object.entries(sanitizers)) {
        if (Object.prototype.hasOwnProperty.call(out, field) === false)
            continue;
        const before = out[field];
        const after = sanitizer(before);
        if (after !== before) {
            out[field] = after;
            changed = true;
        }
    }
    return changed ? out : record;
};
const shouldLogValidationBody = () => {
    if (Env.NODE_ENV === 'production')
        return false;
    return Env.getBool('ZIN_DEBUG_VALIDATION_BODY', false);
};
const safeCall = (fn) => {
    try {
        return fn?.();
    }
    catch {
        return undefined;
    }
};
const getContentType = (req) => {
    const fromGetHeader = safeCall(() => req.getHeader?.('content-type'));
    if (fromGetHeader !== undefined)
        return fromGetHeader;
    const fromHeaders = safeCall(() => req.getHeaders?.()['content-type']);
    if (fromHeaders !== undefined)
        return fromHeaders;
    return req.headers?.['content-type'];
};
const logValidationBodyInput = (req, stage) => {
    if (!shouldLogValidationBody())
        return;
    Logger.debug('[Validation] body input:', {
        stage,
        method: safeCall(() => req.getMethod()),
        path: safeCall(() => req.getPath()),
        contentType: getContentType(req),
        rawBody: safeCall(() => (typeof req.getBody === 'function' ? req.getBody() : undefined)),
        bodyRecord: req.body,
    });
};
const toBodyRecord = (value) => toRecord(value);
const getBodyForValidation = (req) => {
    const bodyRecord = toBodyRecord(req.body);
    if (Object.keys(bodyRecord).length > 0)
        return bodyRecord;
    const raw = safeCall(() => req.getBody?.());
    return toBodyRecord(raw);
};
const handleValidationError = async (req, res, error, options) => {
    // Temporary: log validation error details to help debugging failing requests.
    // Remove this detailed debug logging once the issue is investigated.
    Logger.warn('Validation failed');
    try {
        const err = error;
        if (typeof err?.toObject === 'function') {
            // Prefer structured debug output when available
            try {
                Logger.debug('[Validation] errors:', err.toObject());
            }
            catch {
                // best-effort: fall back to direct log
                Logger.debug('[Validation] errors (toObject threw):', err);
            }
            await respondWithMiddlewareFailure(req, res, options.onFailure, {
                middleware: options.middlewareKey ?? 'validation',
                reason: 'validation_error',
                statusCode: 422,
                message: 'Validation failed',
                body: { errors: err.toObject() },
                error,
            });
            return;
        }
        // Fallback: log raw error
        Logger.debug('[Validation] error:', err);
        await respondWithMiddlewareFailure(req, res, options.onFailure, {
            middleware: options.middlewareKey ?? 'validation',
            reason: 'invalid_request_body',
            statusCode: 400,
            message: 'Invalid request body',
            body: { error: 'Invalid request body' },
            error,
        });
    }
    catch (error_) {
        // Ensure we don't throw while handling validation errors
        Logger.debug('[Validation] failed to log error details:', error_);
        await respondWithMiddlewareFailure(req, res, options.onFailure, {
            middleware: options.middlewareKey ?? 'validation',
            reason: 'invalid_request_body',
            statusCode: 400,
            message: 'Invalid request body',
            body: { error: 'Invalid request body' },
            error,
        });
    }
};
export const ValidationMiddleware = Object.freeze({
    create(schema, options = {}) {
        return async (req, res, next) => {
            const method = req.getMethod();
            if (method === 'GET' || method === 'DELETE') {
                await next();
                return;
            }
            try {
                logValidationBodyInput(req, 'create:before-validate');
                const bodyForValidation = getBodyForValidation(req);
                if (Object.keys(bodyForValidation).length > 0 && Object.keys(req.body ?? {}).length === 0) {
                    safeCall(() => req.setBody?.(bodyForValidation));
                }
                Validator.validate(bodyForValidation, schema);
                req.validated.body = bodyForValidation;
                await next();
            }
            catch (error) {
                await handleValidationError(req, res, error, options);
            }
        };
    },
    createBody(schema, options = {}) {
        return async (req, res, next) => {
            const method = req.getMethod();
            if (method === 'GET' || method === 'DELETE') {
                await next();
                return;
            }
            try {
                logValidationBodyInput(req, 'createBody:before-validate');
                const bodyForValidation = getBodyForValidation(req);
                if (Object.keys(bodyForValidation).length > 0 && Object.keys(req.body ?? {}).length === 0) {
                    safeCall(() => req.setBody?.(bodyForValidation));
                }
                Validator.validate(bodyForValidation, schema);
                req.validated.body = bodyForValidation;
                await next();
            }
            catch (error) {
                await handleValidationError(req, res, error, options);
            }
        };
    },
    createBodyWithSanitization(schema, sanitizers, options = {}) {
        return async (req, res, next) => {
            const method = req.getMethod();
            if (method === 'GET' || method === 'DELETE') {
                await next();
                return;
            }
            try {
                const rawBody = req.getBody();
                const xssSanitized = Xss.sanitize(rawBody ?? {});
                const fieldSanitized = applyFieldSanitizers(xssSanitized, sanitizers);
                const bodyForValidation = toBodyRecord(fieldSanitized);
                safeCall(() => req.setBody?.(bodyForValidation));
                logValidationBodyInput(req, 'createBodyWithSanitization:before-validate');
                Validator.validate(bodyForValidation, schema);
                req.validated.body = bodyForValidation;
                await next();
            }
            catch (error) {
                await handleValidationError(req, res, error, options);
            }
        };
    },
    createQuery(schema, options = {}) {
        return async (req, res, next) => {
            try {
                const query = toRecord(req.getQuery());
                Validator.validate(query, schema);
                req.validated.query = query;
                await next();
            }
            catch (error) {
                await handleValidationError(req, res, error, options);
            }
        };
    },
    createParams(schema, options = {}) {
        return async (req, res, next) => {
            try {
                const params = toRecord(req.getParams());
                Validator.validate(params, schema);
                req.validated.params = params;
                await next();
            }
            catch (error) {
                await handleValidationError(req, res, error, options);
            }
        };
    },
    /**
     * Create body validation middleware with bulletproof sanitization error handling.
     * Automatically converts SanitizerError to 422 validation response.
     * Recommended for authentication, user management, and financial operations.
     *
     * Use this when controllers apply Sanitizer methods with bulletproof=true (default).
     * The middleware will catch SanitizerError and convert to proper validation error response.
     *
     * @param schema - Validation schema
     * @param sanitizers - Optional field sanitizers to apply before validation
     * @returns Middleware with bulletproof error handling
     */
    createBodyWithBulletproofSanitization(schema, sanitizers, options = {}) {
        return async (req, res, next) => {
            const method = req.getMethod();
            if (method === 'GET' || method === 'DELETE') {
                await next();
                return;
            }
            try {
                const rawBody = req.getBody();
                const xssSanitized = Xss.sanitize(rawBody ?? {});
                const fieldSanitized = applyFieldSanitizers(xssSanitized, sanitizers);
                const bodyForValidation = toBodyRecord(fieldSanitized);
                safeCall(() => req.setBody?.(bodyForValidation));
                logValidationBodyInput(req, 'createBodyWithBulletproofSanitization:before-validate');
                Validator.validate(bodyForValidation, schema);
                req.validated.body = bodyForValidation;
                await next();
            }
            catch (error) {
                // Handle SanitizerError by converting to validation error format
                if (isSanitizerError(error)) {
                    await respondWithMiddlewareFailure(req, res, options.onFailure, {
                        middleware: options.middlewareKey ?? 'validation',
                        reason: 'sanitization_error',
                        statusCode: 422,
                        message: error.message,
                        body: {
                            errors: {
                                sanitization: [error.message],
                            },
                        },
                        error,
                    });
                    return;
                }
                await handleValidationError(req, res, error, options);
            }
        };
    },
});
/**
 * Check if error is a SanitizerError
 */
function isSanitizerError(error) {
    return (typeof error === 'object' &&
        error !== null &&
        'name' in error &&
        error.name === 'SanitizerError');
}
