/**
 * Connection Tracker Middleware
 * Hanya mencatat IP yang bisa/tidak bisa mengakses (connection_attempts, isp_statistics).
 * Bukan untuk sistem block maupun whitelist.
 */

const ISPDetector = require('../utils/ispDetector');
// LAZY-LOAD: db tidak di-require di top-level untuk menghindari trigger DB init saat module load
// db akan di-require saat pertama kali dibutuhkan (setelah app.listen dan DB ready)

// In-memory buffer for batch inserts (performance optimization)
const connectionBuffer = [];
const BUFFER_SIZE = 50; // Flush to DB every 50 connections
const BUFFER_TIMEOUT = 5000; // Or every 5 seconds
let flushTimer = null;

class ConnectionTracker {
	/**
	 * Track incoming connection
	 * @param {Object} req - Express request object
	 * @param {Object} res - Express response object
	 * @param {Function} next - Express next function
	 */
	static async track(req, res, next) {
		// ── Runtime toggle: bisa di-disable via dashboard tanpa restart ──────
		let isEnabled = true;
		try {
			const fs = require('fs');
			const path = require('path');
			// app.js creates it in __dirname of app.js, so path from middleware is '__dirname/../tracking-state.json'
			const stateFile = path.resolve(__dirname, '..', 'tracking-state.json');
			if (fs.existsSync(stateFile)) {
				const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
				if (typeof state.enabled === 'boolean') isEnabled = state.enabled;
			}
		} catch (e) {
			// fallback
		}

		if (isEnabled === false) {
			return next(); // tracking dinonaktifkan, skip semua proses
		}


		const startTime = Date.now();
		const ip = req.ip || req.connection.remoteAddress;

		// Skip tracking for dashboard, analytics, diagnostics, health/ready, and static assets
		if (
			req.path.startsWith('/dashboard') ||
			req.path.startsWith('/public') ||
			req.path.startsWith('/favicon') ||
			req.path.startsWith('/api/ip-analytics') ||
			req.path.startsWith('/analytics') ||
			req.path.startsWith('/diagnostics') ||
			req.path === '/health' ||
			req.path === '/health-keepalive' ||
			req.path === '/ready' ||
			req.path === '/ping'
		) {
			return next();
		}

		try {
			// Detect ISP asynchronously (don't block request)
			ConnectionTracker.detectAndLog(req, ip, startTime).catch((err) => {
				console.error('[ConnectionTracker] Background tracking error:', err);
			});

			// Track response
			const originalSend = res.send;
			res.send = function (data) {
				const responseTime = Date.now() - startTime;

				// Log response status
				ConnectionTracker.logResponse(req, ip, res.statusCode, responseTime).catch((err) => {
					console.error('[ConnectionTracker] Response logging error:', err);
				});

				// Call original send
				originalSend.call(this, data);
			};

			next();
		} catch (error) {
			console.error('[ConnectionTracker] Tracking error:', error);
			// Don't block the request even if tracking fails
			next();
		}
	}


	/**
	 * Detect ISP and log connection
	 * @param {Object} req - Express request
	 * @param {string} ip - IP address
	 * @param {number} startTime - Request start time
	 */
	static async detectAndLog(req, ip, startTime) {
		try {
			// Detect ISP
			const ispData = await ISPDetector.detect(ip, req.headers);

			// Prepare connection attempt data
			const connectionData = {
				ip_address: ispData.ip,
				user_agent: req.headers['user-agent'] || null,
				isp_name: ispData.isp,
				isp_detected: ispData.ispStandardized,
				country: ispData.country,
				city: ispData.city,
				asn: ispData.asn,
				asn_org: ispData.asnOrg,
				connection_status: 'success', // Will be updated if fails
				error_type: null,
				error_message: null,
				error_code: null,
				attempt_timestamp: new Date(),
				response_time_ms: null, // Will be set on response
				endpoint_attempted: req.path,
				request_method: req.method,
			};

			// Add to buffer
			connectionBuffer.push(connectionData);

			// Flush if buffer is full
			if (connectionBuffer.length >= BUFFER_SIZE) {
				await ConnectionTracker.flushBuffer();
			} else {
				// Schedule flush
				ConnectionTracker.scheduleFlush();
			}
		} catch (error) {
			console.error('[ConnectionTracker] detectAndLog error:', error);
		}
	}

	/**
	 * Log response status
	 * @param {Object} req - Express request
	 * @param {string} ip - IP address
	 * @param {number} statusCode - HTTP status code
	 * @param {number} responseTime - Response time in ms
	 */
	static async logResponse(req, ip, statusCode, responseTime) {
		try {
			// Find the connection in buffer and update it
			const connectionIndex = connectionBuffer.findIndex(
				(conn) => conn.ip_address === ip && conn.endpoint_attempted === req.path && !conn.response_time_ms
			);

			if (connectionIndex >= 0) {
				connectionBuffer[connectionIndex].response_time_ms = responseTime;

				// Mark as failed if status code indicates error
				if (statusCode >= 500) {
					connectionBuffer[connectionIndex].connection_status = 'failed';
					connectionBuffer[connectionIndex].error_code = statusCode.toString();
				}
			}
		} catch (error) {
			console.error('[ConnectionTracker] logResponse error:', error);
		}
	}

	/**
	 * Schedule buffer flush
	 */
	static scheduleFlush() {
		if (flushTimer) return; // Already scheduled

		flushTimer = setTimeout(async () => {
			flushTimer = null;
			await ConnectionTracker.flushBuffer();
		}, BUFFER_TIMEOUT);
	}

	/**
	 * Flush connection buffer to database
	 * LAZY-LOAD: db di-require di sini (setelah app.listen) untuk menghindari startup delay
	 */
	static async flushBuffer() {
		if (connectionBuffer.length === 0) return;

		try {
			// Lazy-load db (hanya saat pertama kali dipanggil, setelah DB ready)
			let db;
			try {
				db = require('../apps/sapps/helpers/db');
			} catch (dbErr) {
				// db module belum ready atau error - skip flush, data tetap di buffer
				return;
			}

		// Pastikan db dan model ready sebelum pakai
		if (!db || !db.ConnectionAttempt) {
			// DB belum ready - skip flush, data tetap di buffer untuk flush berikutnya
			return;
		}
		
		// Cek apakah connection manager sudah ditutup
		if (db.dbConnectionManager && db.dbConnectionManager.isConnectionManagerClosed()) {
			// Connection manager sudah ditutup (system shutting down) - skip flush
			console.log('[ConnectionTracker] Connection manager closed, skipping flush');
			return;
		}

			// Get connections to flush
			const connectionsToFlush = connectionBuffer.splice(0, connectionBuffer.length);

			// Bulk insert to database
			await db.ConnectionAttempt.bulkCreate(connectionsToFlush, {
				ignoreDuplicates: true,
			});

			console.log(`[ConnectionTracker] Flushed ${connectionsToFlush.length} connection attempts to database`);

			// Update ISP statistics
			await ConnectionTracker.updateISPStatistics(connectionsToFlush);
		} catch (error) {
			console.error('[ConnectionTracker] Error flushing buffer:', error);
			// Re-add failed connections back to buffer (with limit)
			if (connectionBuffer.length < BUFFER_SIZE * 2) {
				// Don't let buffer grow too large
				// connectionBuffer.unshift(...connectionsToFlush);
			}
		}
	}

	/**
	 * Update ISP statistics based on connection attempts
	 * @param {Array} connections - Array of connection attempts
	 * LAZY-LOAD: db di-require di sini untuk menghindari startup delay
	 */
	static async updateISPStatistics(connections) {
		try {
			// Lazy-load db
			let db;
			try {
				db = require('../apps/sapps/helpers/db');
			} catch (dbErr) {
				return; // db belum ready
			}

			if (!db || !db.ISPStatistics) return;

			// Group by ISP
			const ispGroups = {};
			for (const conn of connections) {
				const isp = conn.isp_detected || 'unknown';
				if (!ispGroups[isp]) {
					ispGroups[isp] = {
						total: 0,
						success: 0,
						failed: 0,
						timeout: 0,
						blocked: 0,
						responseTimes: [],
					};
				}

				ispGroups[isp].total++;
				if (conn.connection_status === 'success') {
					ispGroups[isp].success++;
					if (conn.response_time_ms) {
						ispGroups[isp].responseTimes.push(conn.response_time_ms);
					}
				} else if (conn.connection_status === 'failed') {
					ispGroups[isp].failed++;
					// blocked_count: hanya catatan ECONNREFUSED (koneksi ditolak firewall/jaringan), bukan aksi block
					if (conn.error_type === 'ECONNREFUSED') {
						ispGroups[isp].blocked++;
					}
				} else if (conn.connection_status === 'timeout') {
					ispGroups[isp].timeout++;
				}
			}

			// Update each ISP
			for (const [isp, stats] of Object.entries(ispGroups)) {
				const avgResponseTime =
					stats.responseTimes.length > 0
						? stats.responseTimes.reduce((a, b) => a + b, 0) / stats.responseTimes.length
						: 0;

				// Find or create ISP record
				const [ispRecord, created] = await db.ISPStatistics.findOrCreate({
					where: { isp_name: isp },
					defaults: {
						total_attempts: 0,
						successful_attempts: 0,
						failed_attempts: 0,
						timeout_count: 0,
						blocked_count: 0,
						avg_response_time_ms: 0,
						success_rate: 0,
					},
				});

				// Update statistics
				const newTotal = ispRecord.total_attempts + stats.total;
				const newSuccess = ispRecord.successful_attempts + stats.success;
				const newFailed = ispRecord.failed_attempts + stats.failed;
				const newTimeout = ispRecord.timeout_count + stats.timeout;
				const newBlocked = ispRecord.blocked_count + stats.blocked;

				// Calculate new average response time
				const prevTotal = ispRecord.total_attempts || 1;
				const prevAvg = ispRecord.avg_response_time_ms || 0;
				const newAvg = (prevAvg * prevTotal + avgResponseTime * stats.total) / newTotal;

				// Calculate success rate
				const successRate = newTotal > 0 ? (newSuccess / newTotal) * 100 : 0;

				await ispRecord.update({
					total_attempts: newTotal,
					successful_attempts: newSuccess,
					failed_attempts: newFailed,
					timeout_count: newTimeout,
					blocked_count: newBlocked,
					avg_response_time_ms: newAvg.toFixed(2),
					success_rate: successRate.toFixed(2),
					last_success: stats.success > 0 ? new Date() : ispRecord.last_success,
					last_failure: stats.failed > 0 || stats.timeout > 0 ? new Date() : ispRecord.last_failure,
				});
			}
		} catch (error) {
			console.error('[ConnectionTracker] Error updating ISP statistics:', error);
		}
	}

	/**
	 * Log database connection failure
	 * @param {string} ip - IP address
	 * @param {Error} error - Error object
	 * @param {Object} headers - Request headers
	 * LAZY-LOAD: db di-require di sini untuk menghindari startup delay
	 */
	static async logDatabaseFailure(ip, error, headers = {}) {
		if (global.connectionTrackingEnabled === false) return;
		try {
			const ispData = await ISPDetector.detect(ip, headers);

			const connectionData = {
				ip_address: ispData.ip,
				user_agent: headers['user-agent'] || null,
				isp_name: ispData.isp,
				isp_detected: ispData.ispStandardized,
				country: ispData.country,
				city: ispData.city,
				asn: ispData.asn,
				asn_org: ispData.asnOrg,
				connection_status: 'failed',
				error_type: error.code || error.name || 'UNKNOWN',
				error_message: error.message,
				error_code: error.errno || error.code,
				attempt_timestamp: new Date(),
				response_time_ms: null,
				endpoint_attempted: 'DATABASE_CONNECTION',
				request_method: null,
			};

			// Add to buffer
			connectionBuffer.push(connectionData);

			// Flush untuk kegagalan kritis (hanya mencatat, tidak ada aksi block/whitelist)
			// flushBuffer akan lazy-load db jika belum ready
			await ConnectionTracker.flushBuffer();
		} catch (trackError) {
			console.error('[ConnectionTracker] Error logging database failure:', trackError);
		}
	}

	/**
	 * Get tracking statistics
	 * @returns {Object} Current statistics
	 */
	static getStats() {
		return {
			bufferSize: connectionBuffer.length,
			bufferCapacity: BUFFER_SIZE,
			flushScheduled: flushTimer !== null,
		};
	}
}

// Flush buffer on process exit (lazy-load db di flushBuffer)
process.on('beforeExit', async () => {
	console.log('[ConnectionTracker] Flushing buffer before exit...');
	await ConnectionTracker.flushBuffer();
});

// Export middleware function
module.exports = ConnectionTracker.track.bind(ConnectionTracker);

// Export class for manual logging
module.exports.ConnectionTracker = ConnectionTracker;
