/**
 * Connection Resilience Middleware
 * Optimizes connection handling to bypass ISP restrictions and improve reliability
 */

const config = require("../config.json");

/**
 * Configure optimized connection settings
 */
function configureConnectionResilience(app) {
	// ─── TIMEOUT CONFIGURATION ─────────────────────────────────────────
	// Request receive timeout: how long we wait for the client to send the full request body
	const REQUEST_RECEIVE_TIMEOUT = 30000; // 30 seconds (for receiving request body)

	// Response processing timeout: how long we allow the server to process and respond
	// This MUST be longer than the slowest endpoint (authenticate with multiple DB queries)
	const RESPONSE_PROCESSING_TIMEOUT = config.backendConfig?.requestTimeout
		? Math.max(parseInt(config.backendConfig.requestTimeout, 10), 60000)
		: 120000; // minimum 60s, default 120s

	// Heavy endpoints that need extra time (login, /me with hierarchy queries, etc.)
	const HEAVY_ENDPOINT_PATTERNS = [
		'/sapps/users/authenticate',
		'/sapps/users/me',
		'/sapps/users/refresh',
		'/sapps/reports/',
		'/sapps/imports/',
	];
	const HEAVY_ENDPOINT_TIMEOUT = 180000; // 3 minutes for heavy endpoints

	app.use((req, res, next) => {
		// ── 1. REQUEST RECEIVE TIMEOUT ────────────────────────────────────
		// This only applies to receiving the request body from the client.
		// Safe to keep short — we just need the POST body.
		req.setTimeout(REQUEST_RECEIVE_TIMEOUT, () => {
			const clientIP = req.ip || req.socket?.remoteAddress || "unknown";
			console.warn(`[Timeout] Request RECEIVE timeout: ${req.method} ${req.url} from ${clientIP} after ${REQUEST_RECEIVE_TIMEOUT}ms`);

			if (!res.headersSent) {
				res.status(408).json({
					success: false,
					error: {
						type: 'REQUEST_TIMEOUT',
						message: 'Request timed out while receiving data from client.',
						code: 'ETIMEDOUT',
						suggestion: 'Please check your internet connection and try again.'
					}
				});
			}
		});

		// ── 2. RESPONSE PROCESSING TIMEOUT (CUSTOM SAFE TIMER) ─────────
		// DO NOT use res.setTimeout() — it silently destroys the socket
		// without sending any HTTP response, causing ERR_EMPTY_RESPONSE.
		// Instead, use a custom timer that sends a proper 504 response.
		const isHeavyEndpoint = HEAVY_ENDPOINT_PATTERNS.some(p => req.url.includes(p));
		const processingTimeout = isHeavyEndpoint ? HEAVY_ENDPOINT_TIMEOUT : RESPONSE_PROCESSING_TIMEOUT;

		const processingTimer = setTimeout(() => {
			const clientIP = req.ip || req.socket?.remoteAddress || "unknown";
			console.error(`[Timeout] Response PROCESSING timeout: ${req.method} ${req.url} from ${clientIP} after ${processingTimeout}ms`);

			if (!res.headersSent) {
				res.status(504).json({
					success: false,
					error: {
						type: 'PROCESSING_TIMEOUT',
						message: 'The server took too long to process your request.',
						code: 'ETIMEDOUT',
						suggestion: 'The server may be under heavy load. Please try again in a moment.'
					}
				});
			}
		}, processingTimeout);

		// Clear the timer when response is sent (success or error)
		res.on('finish', () => clearTimeout(processingTimer));
		res.on('close', () => clearTimeout(processingTimer));

		// ── 3. CONNECTION HEADER ───────────────────────────────────────────
		// Use keep-alive for better performance; Connection: close hurts
		// performance and can cause issues with browsers reusing connections.
		// Only set close for very old HTTP/1.0 clients.
		if (req.httpVersion === '1.0') {
			res.setHeader("Connection", "close");
		}

		next();
	});

	// Add additional headers to bypass some ISP restrictions
	app.use((req, res, next) => {
		// Prevent ISP caching/modification
		res.setHeader("Cache-Control", "no-transform");

		// Add vary headers for better caching behavior
		res.setHeader("Vary", "Accept-Encoding, Origin");

		next();
	});
}

/**
 * Enhanced error handling for connection issues
 */
function handleConnectionErrors(err, req, res, next) {
	// Log connection-specific errors
	if (err.code === "ECONNRESET" || err.code === "EPIPE" || err.code === "ETIMEDOUT") {
		console.warn(`Connection error from ${req.ip}: ${err.code} - ${err.message}`);

		// Return a specific response for connection issues; Retry-After dari config
		res.set("Retry-After", String(config.backendConfig?.retryAfterSeconds ?? 30));
		return res.status(503).json({
			error: "Connection interrupted",
			code: err.code,
			message: "Please retry your request",
			timestamp: new Date().toISOString(),
		});
	}

	// Pass to next error handler
	next(err);
}

/**
 * Request retry helper middleware
 * Adds retry information to response headers
 */
function addRetryHeaders(req, res, next) {
	// Add headers to help client implement retry logic
	res.setHeader("X-Retry-After", "5"); // Suggest retry after 5 seconds
	res.setHeader("X-RateLimit-Limit", "10000000"); // Match rate limit config
	res.setHeader("X-Request-ID", generateRequestID()); // Unique request ID for tracking

	next();
}

/**
 * Generate unique request ID
 */
function generateRequestID() {
	return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}

/**
 * DNS prefetch and preconnect helper
 * Returns middleware that adds resource hints
 */
function addResourceHints(domains = []) {
	return (req, res, next) => {
		// Add DNS prefetch for external resources
		const hints = domains.map((domain) => `<${domain}>; rel=dns-prefetch`).join(", ");

		if (hints) {
			res.setHeader("Link", hints);
		}

		next();
	};
}

/**
 * Compress response for faster transmission
 * (Works with compression middleware but adds additional headers)
 */
function optimizeTransmission(req, res, next) {
	// Encourage compression
	if (!res.getHeader("Content-Encoding")) {
		res.setHeader("X-Content-Type-Options", "nosniff");
	}

	// Add timing information
	const startTime = Date.now();
	res.on("finish", () => {
		const duration = Date.now() - startTime;
		console.log(`[${req.method}] ${req.url} - ${res.statusCode} - ${duration}ms`);
	});

	next();
}

/**
 * Graceful degradation for problematic requests
 */
function gracefulDegradation(req, res, next) {
	// Handle problematic user agents or patterns
	const userAgent = req.headers["user-agent"] || "";

	// Some ISPs inject their own user agents or modify headers
	if (userAgent.toLowerCase().includes("bot") && !userAgent.toLowerCase().includes("googlebot")) {
		console.warn(`Suspicious user agent from ${req.ip}: ${userAgent}`);
	}

	next();
}

/**
 * IP whitelist/blacklist helper (optional)
 * Can be used to temporarily handle specific ISP ranges
 */
function createIPFilter(whitelist = [], blacklist = []) {
	return (req, res, next) => {
		const clientIP = getClientIP(req);

		// Check blacklist
		if (blacklist.length > 0 && blacklist.some((ip) => clientIP.startsWith(ip))) {
			return res.status(403).json({
				error: "Access denied",
				message: "Your IP range is temporarily restricted",
			});
		}

		// Check whitelist (if defined, only allow whitelisted IPs)
		if (whitelist.length > 0 && !whitelist.some((ip) => clientIP.startsWith(ip))) {
			return res.status(403).json({
				error: "Access denied",
				message: "Your IP is not whitelisted",
			});
		}

		next();
	};
}

/**
 * Get client IP helper
 */
function getClientIP(req) {
	// Proxy headers intentionally ignored; rely on direct connection
	return req.ip || req.socket?.remoteAddress || req.connection?.remoteAddress || "unknown";
}

module.exports = {
	configureConnectionResilience,
	handleConnectionErrors,
	addRetryHeaders,
	addResourceHints,
	optimizeTransmission,
	gracefulDegradation,
	createIPFilter,
};
