import { Env } from './env.js';
import { isArray, isObject } from '../helper/index.js';
import { bodyParsingMiddleware } from '../http/middleware/BodyParsingMiddleware.js';
import { fileUploadMiddleware } from '../http/middleware/FileUploadMiddleware.js';
import { AuthMiddleware } from '../middleware/AuthMiddleware.js';
import { BulletproofAuthMiddleware } from '../middleware/BulletproofAuthMiddleware.js';
import { CsrfMiddleware } from '../middleware/CsrfMiddleware.js';
import { ErrorHandlerMiddleware } from '../middleware/ErrorHandlerMiddleware.js';
import { JwtAuthMiddleware } from '../middleware/JwtAuthMiddleware.js';
import { LoggingMiddleware } from '../middleware/LoggingMiddleware.js';
import { RateLimiter } from '../middleware/RateLimiter.js';
import { SanitizeBodyMiddleware } from '../middleware/SanitizeBodyMiddleware.js';
import { SecurityMiddleware } from '../middleware/SecurityMiddleware.js';
import { ValidationMiddleware } from '../middleware/ValidationMiddleware.js';
import { StartupConfigFile, StartupConfigFileRegistry } from '../runtime/StartupConfigFileRegistry.js';
import { Sanitizer } from '../security/Sanitizer.js';
import { Schema } from '../validation/Validator.js';
export const MiddlewareBody = {
    email: 'email',
    password: 'password', //NOSONAR
    name: 'name',
    count: 'count',
};
export const MiddlewareKeys = Object.freeze({
    log: true,
    error: true,
    security: true,
    rateLimit: true,
    sanitizeBody: true,
    fillRateLimit: true,
    authRateLimit: true,
    userMutationRateLimit: true,
    csrf: true,
    auth: true,
    jwt: true,
    bulletproof: true,
    validateLogin: true,
    validateRegister: true,
    validateUserStore: true,
    validateUserUpdate: true,
    validateUserFill: true,
});
// Ensure `ValidationMiddleware` is strongly typed here to avoid `error`-typed values
// triggering `@typescript-eslint/no-unsafe-*` rules.
const Validation = ValidationMiddleware;
const resolveFillRateLimit = (loadMiddlewareConfig) => {
    return {
        windowMs: loadMiddlewareConfig.fillRateLimit?.windowMs ?? 60_000,
        max: loadMiddlewareConfig.fillRateLimit?.max ?? 5,
        message: loadMiddlewareConfig.fillRateLimit?.message ??
            'Too many requests, please try again after 1 minute.',
    };
};
const resolveAuthRateLimit = (loadMiddlewareConfig) => {
    return {
        windowMs: loadMiddlewareConfig.authRateLimit?.windowMs ?? 60_000,
        max: loadMiddlewareConfig.authRateLimit?.max ?? 10,
        message: loadMiddlewareConfig.authRateLimit?.message ??
            'Too many login attempts, please try again after 1 minute.',
    };
};
const resolveUserMutationRateLimit = (loadMiddlewareConfig) => {
    return {
        windowMs: loadMiddlewareConfig.userMutationRateLimit?.windowMs ?? 60_000,
        max: loadMiddlewareConfig.userMutationRateLimit?.max ?? 20,
        message: loadMiddlewareConfig.userMutationRateLimit?.message ??
            'Too many user requests, please try again after 1 minute.',
    };
};
function createRateLimitMiddlewares(loadMiddlewareConfig, responders) {
    const fillRateLimit = RateLimiter.create({
        ...resolveFillRateLimit(loadMiddlewareConfig),
        onFailure: responders.fillRateLimit,
    });
    const authRateLimit = RateLimiter.create({
        ...resolveAuthRateLimit(loadMiddlewareConfig),
        onFailure: responders.authRateLimit,
    });
    const userMutationRateLimit = RateLimiter.create({
        ...resolveUserMutationRateLimit(loadMiddlewareConfig),
        onFailure: responders.userMutationRateLimit,
    });
    return Object.freeze({
        rateLimit: RateLimiter.create({
            windowMs: 60_000,
            max: 100,
            onFailure: responders.rateLimit,
        }),
        fillRateLimit,
        authRateLimit,
        userMutationRateLimit,
    });
}
function createAuthValidationMiddlewares(responders) {
    return {
        validateLogin: Validation.createBodyWithSanitization(Schema.typed()
            .required(MiddlewareBody.email)
            .email(MiddlewareBody.email)
            .required(MiddlewareBody.password)
            .string(MiddlewareBody.password), {
            email: (v) => Sanitizer.email(v).trim().toLowerCase(),
            password: (v) => Sanitizer.safePasswordChars(v),
        }, { onFailure: responders.validateLogin, middlewareKey: 'validateLogin' }),
        validateRegister: Validation.createBodyWithSanitization(Schema.typed()
            .required(MiddlewareBody.name)
            .string(MiddlewareBody.name)
            .minLength(MiddlewareBody.name, 1)
            .required(MiddlewareBody.email)
            .email(MiddlewareBody.email)
            .required(MiddlewareBody.password)
            .string(MiddlewareBody.password)
            .minLength(MiddlewareBody.password, 8), {
            name: (v) => Sanitizer.nameText(v).trim(),
            email: (v) => Sanitizer.email(v).trim().toLowerCase(),
            password: (v) => Sanitizer.safePasswordChars(v),
        }, { onFailure: responders.validateRegister, middlewareKey: 'validateRegister' }),
    };
}
function createUserValidationMiddlewares(responders) {
    return {
        validateUserStore: Validation.createBodyWithSanitization(Schema.typed()
            .required(MiddlewareBody.name)
            .string(MiddlewareBody.name)
            .minLength(MiddlewareBody.name, 1)
            .required(MiddlewareBody.email)
            .email(MiddlewareBody.email)
            .required(MiddlewareBody.password)
            .string(MiddlewareBody.password)
            .minLength(MiddlewareBody.password, 8), {
            name: (v) => Sanitizer.nameText(v).trim(),
            email: (v) => Sanitizer.email(v).trim().toLowerCase(),
            password: (v) => Sanitizer.safePasswordChars(v),
        }, { onFailure: responders.validateUserStore, middlewareKey: 'validateUserStore' }),
        validateUserUpdate: Validation.createBodyWithSanitization(Schema.typed()
            .custom(MiddlewareBody.name, (v) => v === undefined || typeof v === 'string', MiddlewareBody.name + ' must be a string')
            .minLength(MiddlewareBody.name, 1)
            .custom(MiddlewareBody.email, (v) => v === undefined || typeof v === 'string', MiddlewareBody.email + ' must be a string')
            .custom(MiddlewareBody.password, (v) => v === undefined || typeof v === 'string', MiddlewareBody.password + ' must be a string')
            .minLength(MiddlewareBody.password, 8), {
            name: (v) => Sanitizer.nameText(v).trim(),
            email: (v) => Sanitizer.email(v).trim().toLowerCase(),
            password: (v) => Sanitizer.safePasswordChars(v),
        }, { onFailure: responders.validateUserUpdate, middlewareKey: 'validateUserUpdate' }),
        validateUserFill: Validation.createBodyWithSanitization(Schema.typed()
            .custom(MiddlewareBody.count, (v) => v === undefined || (typeof v === 'number' && Number.isFinite(v)), MiddlewareBody.count + ' must be a number')
            .min(MiddlewareBody.count, 1)
            .max(MiddlewareBody.count, 100), undefined, { onFailure: responders.validateUserFill, middlewareKey: 'validateUserFill' }),
    };
}
function createValidationMiddlewares(responders) {
    return Object.freeze({
        ...createAuthValidationMiddlewares(responders),
        ...createUserValidationMiddlewares(responders),
    });
}
const resolveMiddlewareResponders = (loadMiddlewareConfig) => {
    if (!isObject(loadMiddlewareConfig.responders))
        return {};
    const entries = Object.entries(loadMiddlewareConfig.responders).filter((entry) => {
        const [name, responder] = entry;
        return typeof name === 'string' && name.trim() !== '' && typeof responder === 'function';
    });
    return Object.freeze(Object.fromEntries(entries));
};
function createSharedMiddlewares(loadMiddlewareConfig, responders) {
    const rateLimits = createRateLimitMiddlewares(loadMiddlewareConfig, responders);
    const validations = createValidationMiddlewares(responders);
    return Object.freeze({
        log: LoggingMiddleware.create(),
        error: ErrorHandlerMiddleware.create({ onFailure: responders.error }),
        security: SecurityMiddleware.create(),
        sanitizeBody: SanitizeBodyMiddleware.create(),
        ...rateLimits,
        csrf: CsrfMiddleware.create({
            skipPaths: loadMiddlewareConfig?.skipPaths ?? [],
            onFailure: responders.csrf,
        }),
        auth: AuthMiddleware.create({ onUnauthorized: responders.auth }),
        jwt: JwtAuthMiddleware.create({ onUnauthorized: responders.jwt }),
        bulletproof: BulletproofAuthMiddleware.create({ onUnauthorized: responders.bulletproof }),
        ...validations,
    });
}
const resolveProjectGlobalMiddlewares = (loadMiddlewareConfig) => {
    if (!isArray(loadMiddlewareConfig.global))
        return [];
    return loadMiddlewareConfig.global.filter((middleware) => {
        return typeof middleware === 'function';
    });
};
const resolveProjectRouteMiddlewares = (loadMiddlewareConfig) => {
    if (!isObject(loadMiddlewareConfig.route))
        return {};
    const entries = Object.entries(loadMiddlewareConfig.route).filter((entry) => {
        const [name, middleware] = entry;
        return typeof name === 'string' && name.trim() !== '' && typeof middleware === 'function';
    });
    return Object.fromEntries(entries);
};
const applySharedMiddlewareOverrides = (shared, projectRoute) => {
    const overridden = { ...shared };
    for (const key of Object.keys(MiddlewareKeys)) {
        const candidate = projectRoute[key];
        if (typeof candidate === 'function') {
            overridden[key] = candidate;
        }
    }
    return Object.freeze(overridden);
};
export function createMiddlewareConfig() {
    const loadMiddlewareConfig = StartupConfigFileRegistry.get(StartupConfigFile.Middleware) ?? {};
    const skipPathsFromEnv = Env.get('CSRF_SKIP_PATHS', '')
        .split(',')
        .map((path) => path.trim())
        .filter((path) => path.length > 0);
    const effectiveMiddlewareConfig = {
        ...loadMiddlewareConfig,
        skipPaths: Array.isArray(loadMiddlewareConfig.skipPaths) && loadMiddlewareConfig.skipPaths.length > 0
            ? loadMiddlewareConfig.skipPaths
            : skipPathsFromEnv,
    };
    const responders = resolveMiddlewareResponders(effectiveMiddlewareConfig);
    const shared = createSharedMiddlewares(effectiveMiddlewareConfig, responders);
    const projectGlobal = resolveProjectGlobalMiddlewares(effectiveMiddlewareConfig);
    const projectRoute = resolveProjectRouteMiddlewares(effectiveMiddlewareConfig);
    const resolvedShared = applySharedMiddlewareOverrides(shared, projectRoute);
    const middlewareConfigObj = {
        global: [
            resolvedShared.log,
            resolvedShared.error,
            resolvedShared.security,
            resolvedShared.rateLimit,
            fileUploadMiddleware,
            bodyParsingMiddleware,
            resolvedShared.csrf,
            resolvedShared.sanitizeBody,
            ...projectGlobal,
        ],
        route: {
            ...resolvedShared,
            ...projectRoute,
        },
    };
    return Object.freeze(middlewareConfigObj);
}
let cached = null;
/**
 * Clear the middleware configuration cache
 * This ensures fresh config loading in watch mode
 */
export const clearMiddlewareConfigCache = () => {
    cached = null;
};
// Proxy target must satisfy JS Proxy invariants.
// When we lazily create a frozen config object (with non-configurable properties),
// we mirror its property descriptors onto this target so reflective operations
// like Object.getOwnPropertyDescriptor() do not throw.
const proxyTarget = {};
function ensureMiddlewareConfig() {
    if (cached)
        return cached;
    cached = createMiddlewareConfig();
    try {
        Object.defineProperties(proxyTarget, Object.getOwnPropertyDescriptors(cached));
    }
    catch {
        // best-effort; proxy still functions via `get` trap even if reflection fails
    }
    return cached;
}
export const middlewareConfig = new Proxy(proxyTarget, {
    get(_target, prop) {
        return ensureMiddlewareConfig()[prop];
    },
    ownKeys() {
        ensureMiddlewareConfig();
        return Reflect.ownKeys(proxyTarget);
    },
    getOwnPropertyDescriptor(_target, prop) {
        ensureMiddlewareConfig();
        return Object.getOwnPropertyDescriptor(proxyTarget, prop);
    },
});
export default middlewareConfig;
