/**
 * Network Diagnostics Utility
 * Test network connectivity to database server before attempting connection
 */

const net = require('net');
const dns = require('dns').promises;

class NetworkDiagnostics {
	/**
	 * Test TCP connection to host:port with retry mechanism
	 * @param {string} host - Database host
	 * @param {number} port - Database port
	 * @param {number} timeout - Timeout in milliseconds (default: 10000)
	 * @param {Object} options - Additional options
	 * @param {number} options.maxRetries - Maximum retry attempts (default: 0)
	 * @param {number} options.retryDelay - Delay between retries in ms (default: 1000)
	 * @returns {Promise<Object>} Diagnostic result
	 */
	static async testTCPConnection(host, port, timeout = 10000, options = {}) {
		const maxRetries = options.maxRetries || 0;
		const retryDelay = options.retryDelay || 1000;

		if (maxRetries === 0) {
			return await this._testTCPConnectionOnce(host, port, timeout);
		}

		// Retry logic
		let lastError = null;
		for (let attempt = 0; attempt <= maxRetries; attempt++) {
			const result = await this._testTCPConnectionOnce(host, port, timeout);
			
			if (result.success) {
				return {
					...result,
					attempts: attempt + 1,
					totalAttempts: attempt + 1
				};
			}

			lastError = result;

			// Don't wait after last attempt
			if (attempt < maxRetries) {
				const delay = retryDelay * Math.pow(2, attempt); // Exponential backoff
				await new Promise(resolve => setTimeout(resolve, delay));
			}
		}

		return {
			...lastError,
			attempts: maxRetries + 1,
			totalAttempts: maxRetries + 1,
			allAttemptsFailed: true
		};
	}

	/**
	 * Single TCP connection test
	 * @private
	 */
	static async _testTCPConnectionOnce(host, port, timeout = 10000) {
		return new Promise((resolve) => {
			const startTime = Date.now();
			const socket = new net.Socket();
			let resolved = false;

			const cleanup = () => {
				if (!resolved) {
					resolved = true;
					try {
						socket.destroy();
					} catch (_) {}
				}
			};

			const timeoutId = setTimeout(() => {
				cleanup();
				resolve({
					success: false,
					error: 'ETIMEDOUT',
					message: `TCP connection timeout after ${timeout}ms`,
					duration: Date.now() - startTime,
					suggestion: 'Connection timeout. Database server mungkin tidak dapat diakses dari network Anda. Coba gunakan VPN kantor atau periksa firewall rules.'
				});
			}, timeout);

			socket.on('connect', () => {
				cleanup();
				clearTimeout(timeoutId);
				const duration = Date.now() - startTime;
				resolve({
					success: true,
					message: 'TCP connection successful',
					duration,
					host,
					port
				});
			});

			socket.on('error', (err) => {
				cleanup();
				clearTimeout(timeoutId);
				const duration = Date.now() - startTime;
				
				// Classify error
				let suggestion = '';
				let errorType = 'NETWORK_ERROR';
				
				switch (err.code) {
					case 'ECONNREFUSED':
						errorType = 'FIREWALL_BLOCKED';
						suggestion = 'Connection refused. Database server mungkin memblokir koneksi dari IP Anda. Kemungkinan besar Anda perlu menggunakan VPN kantor untuk mengakses database server.';
						break;
					case 'ETIMEDOUT':
						errorType = 'TIMEOUT';
						suggestion = 'Connection timeout. Database server tidak merespons. Kemungkinan firewall memblokir koneksi atau server tidak dapat diakses dari network Anda. Coba gunakan VPN kantor.';
						break;
					case 'EHOSTUNREACH':
					case 'ENETUNREACH':
						errorType = 'NETWORK_UNREACHABLE';
						suggestion = 'Host/Network tidak dapat dijangkau. Kemungkinan masalah routing network atau firewall memblokir akses. Coba gunakan VPN kantor.';
						break;
					case 'ENOTFOUND':
						errorType = 'DNS_ERROR';
						suggestion = 'Host tidak ditemukan. Periksa konfigurasi hostname/IP address.';
						break;
					default:
						errorType = 'UNKNOWN_NETWORK_ERROR';
						suggestion = `Network error: ${err.code}. Periksa koneksi network dan firewall rules. Jika menggunakan IP publik, mungkin perlu VPN kantor.`;
				}

				resolve({
					success: false,
					error: err.code,
					errorType,
					message: err.message,
					duration,
					host,
					port,
					suggestion
				});
			});

			try {
				socket.connect(port, host);
			} catch (err) {
				cleanup();
				clearTimeout(timeoutId);
				resolve({
					success: false,
					error: err.code || 'UNKNOWN',
					message: err.message,
					duration: Date.now() - startTime,
					suggestion: 'Gagal membuat koneksi. Periksa konfigurasi host dan port.'
				});
			}
		});
	}

	/**
	 * Test TCP connection with multiple strategies
	 * @param {string} host - Database host
	 * @param {number} port - Database port
	 * @param {Object} options - Options
	 * @returns {Promise<Object>} Diagnostic result
	 */
	static async testTCPConnectionWithStrategies(host, port, options = {}) {
		const strategies = [
			{ timeout: 5000, name: 'quick' },
			{ timeout: 10000, name: 'standard' },
			{ timeout: 15000, name: 'extended' }
		];

		const results = [];
		for (const strategy of strategies) {
			const result = await this._testTCPConnectionOnce(host, port, strategy.timeout);
			results.push({
				strategy: strategy.name,
				timeout: strategy.timeout,
				...result
			});

			if (result.success) {
				return {
					success: true,
					strategy: strategy.name,
					results,
					message: `Connection successful with ${strategy.name} strategy`
				};
			}
		}

		// All strategies failed
		const lastResult = results[results.length - 1];
		return {
			success: false,
			strategy: 'all_failed',
			results,
			error: lastResult.error,
			errorType: lastResult.errorType,
			message: 'All connection strategies failed',
			suggestion: lastResult.suggestion
		};
	}

	/**
	 * Test DNS resolution (if using hostname)
	 * @param {string} host - Hostname or IP
	 * @returns {Promise<Object>} DNS resolution result
	 */
	static async testDNSResolution(host) {
		// If it's an IP address, skip DNS test
		if (net.isIP(host)) {
			return {
				success: true,
				message: 'IP address provided, DNS resolution not needed',
				ip: host
			};
		}

		try {
			const addresses = await dns.resolve4(host);
			return {
				success: true,
				message: 'DNS resolution successful',
				host,
				ip: addresses[0]
			};
		} catch (error) {
			return {
				success: false,
				error: error.code,
				message: error.message,
				host,
				suggestion: 'DNS resolution failed. Periksa konfigurasi DNS atau gunakan IP address langsung.'
			};
		}
	}

	/**
	 * Comprehensive network diagnostic
	 * @param {string} host - Database host
	 * @param {number} port - Database port
	 * @param {number} timeout - Timeout in milliseconds
	 * @returns {Promise<Object>} Complete diagnostic result
	 */
	static async diagnose(host, port, timeout = 10000) {
		const results = {
			timestamp: new Date().toISOString(),
			host,
			port,
			tests: {}
		};

		// Test DNS resolution (if needed)
		const dnsResult = await this.testDNSResolution(host);
		results.tests.dns = dnsResult;

		// Test TCP connection
		const tcpResult = await this.testTCPConnection(host, port, timeout);
		results.tests.tcp = tcpResult;

		// Overall status
		results.success = tcpResult.success;
		results.summary = tcpResult.success
			? 'Network connectivity test passed. Database server is reachable.'
			: `Network connectivity test failed: ${tcpResult.error || 'Unknown error'}. ${tcpResult.suggestion || ''}`;

		// Recommendations
		results.recommendations = [];
		if (!tcpResult.success) {
			if (tcpResult.errorType === 'FIREWALL_BLOCKED' || tcpResult.errorType === 'TIMEOUT') {
				results.recommendations.push('Gunakan VPN kantor untuk mengakses database server');
				results.recommendations.push('Periksa firewall rules di database server');
				results.recommendations.push('Pastikan IP Anda dalam whitelist database server');
			}
			if (tcpResult.errorType === 'NETWORK_UNREACHABLE') {
				results.recommendations.push('Periksa network routing configuration');
				results.recommendations.push('Coba gunakan VPN kantor');
			}
		}

		return results;
	}

	/**
	 * Get user-friendly error message based on network error
	 * @param {Error} error - Network error
	 * @returns {Object} Error information with suggestion
	 */
	static getNetworkErrorInfo(error) {
		const errorCode = error.code || error.name || '';
		const errorMessage = error.message || '';

		let errorType = 'UNKNOWN';
		let userMessage = 'Terjadi kesalahan network';
		let suggestion = 'Periksa koneksi network Anda';

		switch (errorCode) {
			case 'ECONNREFUSED':
				errorType = 'FIREWALL_BLOCKED';
				userMessage = 'Koneksi ke database server ditolak';
				suggestion = 'Database server mungkin memblokir koneksi dari IP Anda. Kemungkinan besar Anda perlu menggunakan VPN kantor untuk mengakses database server.';
				break;

			case 'ETIMEDOUT':
				errorType = 'TIMEOUT';
				userMessage = 'Koneksi ke database server timeout';
				suggestion = 'Database server tidak merespons. Kemungkinan firewall memblokir koneksi atau server tidak dapat diakses dari network Anda. Coba gunakan VPN kantor.';
				break;

			case 'EHOSTUNREACH':
			case 'ENETUNREACH':
				errorType = 'NETWORK_UNREACHABLE';
				userMessage = 'Database server tidak dapat dijangkau';
				suggestion = 'Network tidak dapat mencapai database server. Kemungkinan masalah routing atau firewall. Coba gunakan VPN kantor.';
				break;

			case 'ENOTFOUND':
				errorType = 'DNS_ERROR';
				userMessage = 'Host database tidak ditemukan';
				suggestion = 'DNS resolution gagal. Periksa konfigurasi hostname atau gunakan IP address langsung.';
				break;

			default:
				if (errorMessage.includes('timeout') || errorMessage.includes('Timeout')) {
					errorType = 'TIMEOUT';
					userMessage = 'Koneksi timeout';
					suggestion = 'Koneksi timeout. Database server mungkin tidak dapat diakses dari network Anda. Coba gunakan VPN kantor.';
				} else if (errorMessage.includes('refused') || errorMessage.includes('Refused')) {
					errorType = 'FIREWALL_BLOCKED';
					userMessage = 'Koneksi ditolak';
					suggestion = 'Koneksi ditolak oleh database server. Kemungkinan perlu VPN kantor.';
				}
		}

		return {
			errorType,
			errorCode,
			userMessage,
			suggestion,
			technicalMessage: errorMessage
		};
	}
}

module.exports = NetworkDiagnostics;
