/**
 * ISP Blocking Detection & Analytics Middleware
 * Tracks and analyzes patterns that might indicate ISP blocking
 */

const fs = require("fs");
const path = require("path");

// Ensure log directory exists once at module load (avoids existsSync/mkdirSync in hot path under 5xx load)
const logDir = path.join(__dirname, "../logs");
if (!fs.existsSync(logDir)) {
	fs.mkdirSync(logDir, { recursive: true });
}

// Store analytics data
const analytics = {
	requests: [],
	ispPatterns: new Map(),
	failurePatterns: new Map(),
	maxStoredRequests: 1000, // Keep last 1000 requests
};

/**
 * Track request with detailed information
 */
function trackRequest(req, res, next) {
	const requestData = {
		timestamp: new Date().toISOString(),
		ip: getClientIP(req),
		method: req.method,
		url: req.url,
		userAgent: req.headers["user-agent"],
		origin: req.headers.origin,
		referer: req.headers.referer,
		forwardedFor: req.headers["x-forwarded-for"],
		realIP: req.headers["x-real-ip"],
		cfConnectingIP: req.headers["cf-connecting-ip"],
		country: req.headers["cf-ipcountry"] || "unknown",
		statusCode: null,
		responseTime: null,
	};

	const startTime = Date.now();

	// Capture response data
	res.on("finish", () => {
		requestData.statusCode = res.statusCode;
		requestData.responseTime = Date.now() - startTime;

		// Store request data
		analytics.requests.push(requestData);

		// Maintain max stored requests
		if (analytics.requests.length > analytics.maxStoredRequests) {
			analytics.requests.shift();
		}

		// Analyze for ISP patterns
		analyzeISPPattern(requestData);

		// Log suspicious patterns
		if (requestData.statusCode >= 500 || requestData.responseTime > 30000) {
			logSuspiciousRequest(requestData);
		}
	});

	next();
}

/**
 * Analyze ISP patterns from request data
 */
function analyzeISPPattern(requestData) {
	const ip = requestData.ip;
	const ipRange = getIPRange(ip);

	// Track ISP/IP range statistics
	if (!analytics.ispPatterns.has(ipRange)) {
		analytics.ispPatterns.set(ipRange, {
			ipRange,
			totalRequests: 0,
			successfulRequests: 0,
			failedRequests: 0,
			avgResponseTime: 0,
			firstSeen: requestData.timestamp,
			lastSeen: requestData.timestamp,
			userAgents: new Set(),
			countries: new Set(),
		});
	}

	const pattern = analytics.ispPatterns.get(ipRange);
	pattern.totalRequests++;
	pattern.lastSeen = requestData.timestamp;

	if (requestData.statusCode >= 200 && requestData.statusCode < 400) {
		pattern.successfulRequests++;
	} else {
		pattern.failedRequests++;
	}

	// Update average response time
	pattern.avgResponseTime = (pattern.avgResponseTime * (pattern.totalRequests - 1) + requestData.responseTime) / pattern.totalRequests;

	// Track user agents and countries
	if (requestData.userAgent) pattern.userAgents.add(requestData.userAgent);
	if (requestData.country && requestData.country !== "unknown") pattern.countries.add(requestData.country);

	// Detect potential blocking
	if (pattern.totalRequests > 10) {
		const failureRate = (pattern.failedRequests / pattern.totalRequests) * 100;
		if (failureRate > 50) {
			console.warn(`⚠️ High failure rate detected for IP range ${ipRange}: ${failureRate.toFixed(2)}%`);
			logPotentialBlocking(ipRange, pattern);
		}
	}
}

/**
 * Get IP range (first 3 octets for IPv4)
 */
function getIPRange(ip) {
	if (ip.includes(".")) {
		// IPv4
		const parts = ip.split(".");
		return `${parts[0]}.${parts[1]}.${parts[2]}.*`;
	} else if (ip.includes(":")) {
		// IPv6 - use first 4 segments
		const parts = ip.split(":");
		return `${parts[0]}:${parts[1]}:${parts[2]}:${parts[3]}:*`;
	}
	return "unknown";
}

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

/**
 * Log suspicious requests to file (async; no sync I/O in hot path to avoid blocking under 5xx load)
 */
function logSuspiciousRequest(requestData) {
	const logFile = path.join(logDir, "suspicious-requests.log");
	const logEntry = `${requestData.timestamp} | IP: ${requestData.ip} | ${requestData.method} ${requestData.url} | Status: ${requestData.statusCode} | Time: ${requestData.responseTime}ms | UA: ${requestData.userAgent}\n`;

	fs.appendFile(logFile, logEntry, (err) => {
		if (err) console.error("Failed to write suspicious request log:", err);
	});
}

/**
 * Log potential ISP blocking (async; no sync I/O in hot path to avoid blocking under load)
 */
function logPotentialBlocking(ipRange, pattern) {
	const logFile = path.join(logDir, "isp-blocking-alerts.log");
	const failureRate = ((pattern.failedRequests / pattern.totalRequests) * 100).toFixed(2);
	const logEntry = `${new Date().toISOString()} | IP Range: ${ipRange} | Total: ${pattern.totalRequests} | Failed: ${
		pattern.failedRequests
	} (${failureRate}%) | Avg Response: ${pattern.avgResponseTime.toFixed(2)}ms | Countries: ${Array.from(pattern.countries).join(", ")}\n`;

	fs.appendFile(logFile, logEntry, (err) => {
		if (err) console.error("Failed to write ISP blocking alert:", err);
	});
}

/**
 * Get analytics summary
 */
function getAnalyticsSummary() {
	const topBlockedRanges = Array.from(analytics.ispPatterns.values())
		.filter((p) => p.totalRequests > 5)
		.map((p) => ({
			ipRange: p.ipRange,
			totalRequests: p.totalRequests,
			failureRate: `${((p.failedRequests / p.totalRequests) * 100).toFixed(2)}%`,
			avgResponseTime: `${p.avgResponseTime.toFixed(2)}ms`,
			countries: Array.from(p.countries),
		}))
		.sort((a, b) => parseFloat(b.failureRate) - parseFloat(a.failureRate))
		.slice(0, 20);

	return {
		totalRequestsTracked: analytics.requests.length,
		totalIPRanges: analytics.ispPatterns.size,
		topBlockedRanges,
		recentRequests: analytics.requests.slice(-10).map((r) => ({
			timestamp: r.timestamp,
			ip: r.ip,
			method: r.method,
			url: r.url,
			statusCode: r.statusCode,
			responseTime: r.responseTime ? `${r.responseTime}ms` : "N/A",
		})),
	};
}

/**
 * Get detailed ISP pattern report
 */
function getISPPatternReport() {
	const report = [];

	analytics.ispPatterns.forEach((pattern, ipRange) => {
		const failureRate = (pattern.failedRequests / pattern.totalRequests) * 100;

		report.push({
			ipRange,
			totalRequests: pattern.totalRequests,
			successful: pattern.successfulRequests,
			failed: pattern.failedRequests,
			failureRate: `${failureRate.toFixed(2)}%`,
			avgResponseTime: `${pattern.avgResponseTime.toFixed(2)}ms`,
			firstSeen: pattern.firstSeen,
			lastSeen: pattern.lastSeen,
			uniqueUserAgents: pattern.userAgents.size,
			countries: Array.from(pattern.countries),
			potentiallyBlocked: failureRate > 50 && pattern.totalRequests > 10,
		});
	});

	return report.sort((a, b) => b.totalRequests - a.totalRequests);
}

/**
 * Clear old analytics data
 */
function clearOldAnalytics(daysToKeep = 7) {
	const cutoffDate = new Date();
	cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);

	analytics.requests = analytics.requests.filter((r) => new Date(r.timestamp) > cutoffDate);

	console.log(`Analytics data older than ${daysToKeep} days cleared.`);
}

module.exports = {
	trackRequest,
	getAnalyticsSummary,
	getISPPatternReport,
	clearOldAnalytics,
};
