import { Request, Response, NextFunction } from 'express';
import Logger from '#utils/logger';

/**
 * HTTPS Redirect Middleware
 *
 * Redirects all HTTP requests to HTTPS in production/staging environments.
 * Should be registered as the FIRST middleware in the Express chain,
 * before any other middleware (especially CORS), to avoid processing
 * plaintext requests unnecessarily.
 *
 * This middleware does NOT run in development (NODE_ENV=development).
 *
 * SECURITY NOTES:
 * - Uses 308 (Permanent Redirect) to preserve HTTP method and body
 * - Does not redirect WebSocket upgrades (ws://) — handled by the reverse proxy
 * - Does not redirect health checks from load balancer probes (if behind a proxy)
 * - X-Forwarded-Proto header check supports reverse proxy setups (nginx, cloud LB)
 */

export interface HttpsRedirectOptions {
	/** Port to redirect to (default: 443) */
	httpsPort?: number | string;
	/** Trust X-Forwarded-Proto from these proxy IPs (default: loopback only) */
	trustedProxies?: string[];
	/** Skip redirect for these paths (e.g. health checks) */
	skipPaths?: string[];
}

/**
 * Check if the request is already behind a trusted proxy and the frontend
 * reports HTTPS (important when running behind an SSL-terminating reverse proxy).
 */
function isSecureBehindProxy(req: Request, trustedProxies: string[]): boolean {
	const clientIp = req.ip || '';
	const forwardedProto = req.header('X-Forwarded-Proto');
	const realSslHeader = req.header('X-Url-Scheme');

	// If no proxy headers are set, trust direct connection security
	if (!forwardedProto && !realSslHeader) {
		return false;
	}

	// Verify the request came from a trusted proxy before trusting forwarded headers
	// eslint-disable-next-line no-restricted-syntax
	const isTrusted = trustedProxies.some((proxy) => {
		if (proxy === '*') return true;
		return clientIp === proxy || clientIp.startsWith(proxy);
	});

	if (!isTrusted) {
		return false;
	}

	const scheme = realSslHeader || forwardedProto;
	return scheme === 'https';
}

export function httpsRedirect(options: HttpsRedirectOptions = {}) {
	const {
		httpsPort = 443,
		trustedProxies = ['127.0.0.1', '::1'],
		skipPaths = ['/health', '/healthz', '/ready', '/readyz', '/favicon.ico'],
	} = options;

	return (req: Request, _res: Response, next: NextFunction): void => {
		// Only redirect in non-development environments
		// eslint-disable-next-line no-restricted-syntax
		if (process.env.NODE_ENV === 'development') {
			return next();
		}

		// Skip specified paths (health checks, etc.)
		if (skipPaths.some((p) => req.path === p || req.path.startsWith(p + '/'))) {
			return next();
		}

		// Already HTTPS or localhost (localhost is always HTTP in dev)
		if (req.secure || req.protocol === 'https') {
			return next();
		}

		// Trust proxy headers if request comes from a trusted proxy
		if (isSecureBehindProxy(req, trustedProxies)) {
			return next();
		}

		// Build redirect URL preserving path and query
		const host = req.header('Host') || 'localhost';
		const port = typeof httpsPort === 'string' ? parseInt(httpsPort, 10) : httpsPort;
		const forwardedPort = req.header('X-Forwarded-Port');
		const portStr =
			forwardedPort || (port !== 443 ? `:${port}` : '');

		const protocol = 'https';
		const url = `${protocol}://${host}${portStr}${req.originalUrl}`;

		// eslint-disable-next-line no-console
		if (process.env.NODE_ENV === 'production') {
			Logger.warn(`[HttpsRedirect] Non-HTTPS request: ${req.method} ${req.originalUrl} → redirecting`);
		}

		// Use 308 to preserve method (POST/PUT won't become GET)
		return _res.redirect(308, url);
	};
}
