/**
 * ISP Detector Utility
 * Detects ISP from IP address using multiple methods
 * Priority: Headers > GeoIP > ASN > Pattern Matching > DNS
 */

const geoip = require('geoip-lite');
const dns = require('dns').promises;

// Known ISP patterns for Indonesia
const ISP_PATTERNS = {
	telkomsel: {
		names: ['telkomsel', 'telkomsel indonesia', 'pt telekomunikasi selular'],
		asns: ['AS4761', 'AS23693'],
		prefixes: [], // Can add specific IP prefixes if known
	},
	indihome: {
		names: ['telkom', 'telkom indonesia', 'pt telekomunikasi indonesia', 'indihome'],
		asns: ['AS7713', 'AS17974'],
		prefixes: [],
	},
	xl: {
		names: ['xl axiata', 'pt xl axiata', 'xl'],
		asns: ['AS24203'],
		prefixes: [],
	},
	tri: {
		names: ['hutchison', '3 indonesia', 'tri indonesia', 'hutchison 3 indonesia'],
		asns: ['AS45727'],
		prefixes: [],
	},
	smartfren: {
		names: ['smartfren', 'pt smartfren telecom'],
		asns: ['AS56300'],
		prefixes: [],
	},
	biznet: {
		names: ['biznet', 'pt supra primatama nusantara'],
		asns: ['AS17451'],
		prefixes: [],
	},
	// Add more ISPs as needed
};

class ISPDetector {
	/**
	 * Detect ISP from IP address
	 * @param {string} ipAddress - IP address to detect
	 * @param {Object} headers - HTTP request headers (optional)
	 * @returns {Object} ISP information
	 */
	static async detect(ipAddress, headers = {}) {
		try {
			// Skip localhost and private IPs
			if (this.isPrivateIP(ipAddress)) {
				return {
					ip: ipAddress,
					isp: 'local',
					ispStandardized: 'local',
					country: 'local',
					city: 'local',
					asn: null,
					asnOrg: null,
					confidence: 'high',
					method: 'pattern',
				};
			}

			// Get real IP from headers if behind proxy
			const realIP = this.extractRealIP(ipAddress, headers);

			// Try GeoIP lookup first (fastest)
			const geoData = geoip.lookup(realIP);
			let isp = null;
			let ispStandardized = 'unknown';
			let country = 'unknown';
			let city = 'unknown';
			let asn = null;
			let asnOrg = null;
			let confidence = 'low';
			let method = 'none';

			if (geoData) {
				country = geoData.country || 'unknown';
				city = geoData.city || 'unknown';

				// geoip-lite doesn't have ISP info, but we can use country/city
				method = 'geoip';
				confidence = 'medium';
			}

			// Try to detect ISP from reverse DNS
			try {
				const hostnames = await dns.reverse(realIP);
				if (hostnames && hostnames.length > 0) {
					const hostname = hostnames[0].toLowerCase();
					isp = hostname;

					// Match against known ISP patterns
					const matched = this.matchISPPattern(hostname);
					if (matched) {
						ispStandardized = matched;
						confidence = 'high';
						method = 'reverse_dns';
					}
				}
			} catch (dnsError) {
				// Reverse DNS lookup failed, continue with other methods
			}

			// If still unknown, try user agent or other patterns
			if (ispStandardized === 'unknown' && headers['user-agent']) {
				const userAgent = headers['user-agent'].toLowerCase();
				// Some mobile ISPs can be detected from user agent
				if (userAgent.includes('telkomsel')) ispStandardized = 'telkomsel';
				else if (userAgent.includes('indihome')) ispStandardized = 'indihome';
				else if (userAgent.includes('xl')) ispStandardized = 'xl';
			}

			return {
				ip: realIP,
				isp: isp,
				ispStandardized: ispStandardized,
				country: country,
				city: city,
				asn: asn,
				asnOrg: asnOrg,
				confidence: confidence,
				method: method,
			};
		} catch (error) {
			console.error('[ISPDetector] Error detecting ISP:', error);
			return {
				ip: ipAddress,
				isp: 'error',
				ispStandardized: 'unknown',
				country: 'error',
				city: 'error',
				asn: null,
				asnOrg: null,
				confidence: 'none',
				method: 'error',
				error: error.message,
			};
		}
	}

	/**
	 * Extract real IP from headers (handle proxies, load balancers)
	 * @param {string} ipAddress - Direct IP
	 * @param {Object} headers - HTTP headers
	 * @returns {string} Real IP address
	 */
	static extractRealIP(ipAddress, headers = {}) {
		// Check common proxy headers in order of priority
		const forwardedFor = headers['x-forwarded-for'];
		if (forwardedFor) {
			// X-Forwarded-For can contain multiple IPs, get the first (original client)
			const ips = forwardedFor.split(',').map((ip) => ip.trim());
			if (ips.length > 0 && !this.isPrivateIP(ips[0])) {
				return ips[0];
			}
		}

		const realIP = headers['x-real-ip'];
		if (realIP && !this.isPrivateIP(realIP)) {
			return realIP;
		}

		const cfConnectingIP = headers['cf-connecting-ip']; // Cloudflare
		if (cfConnectingIP && !this.isPrivateIP(cfConnectingIP)) {
			return cfConnectingIP;
		}

		const trueClientIP = headers['true-client-ip']; // Akamai
		if (trueClientIP && !this.isPrivateIP(trueClientIP)) {
			return trueClientIP;
		}

		return ipAddress;
	}

	/**
	 * Check if IP is private/local
	 * @param {string} ip - IP address
	 * @returns {boolean}
	 */
	static isPrivateIP(ip) {
		if (!ip) return true;

		// IPv4 private ranges
		const ipv4Private = /^(127\.)|(10\.)|(172\.1[6-9]\.)|(172\.2[0-9]\.)|(172\.3[0-1]\.)|(192\.168\.)/;
		if (ipv4Private.test(ip)) return true;

		// Localhost
		if (ip === '::1' || ip === 'localhost') return true;

		// IPv6 private
		if (ip.startsWith('fe80:') || ip.startsWith('fc00:') || ip.startsWith('fd00:')) return true;

		return false;
	}

	/**
	 * Match hostname/ISP name against known patterns
	 * @param {string} text - Text to match
	 * @returns {string|null} Standardized ISP name or null
	 */
	static matchISPPattern(text) {
		if (!text) return null;
		const lowerText = text.toLowerCase();

		for (const [key, config] of Object.entries(ISP_PATTERNS)) {
			// Check if any name matches
			for (const name of config.names) {
				if (lowerText.includes(name)) {
					return key;
				}
			}
		}

		return null;
	}

	/**
	 * Get ISP statistics summary
	 * @returns {Object} ISP patterns configuration
	 */
	static getSupportedISPs() {
		return Object.keys(ISP_PATTERNS);
	}

	/**
	 * Add custom ISP pattern
	 * @param {string} key - ISP identifier
	 * @param {Object} pattern - ISP pattern configuration
	 */
	static addISPPattern(key, pattern) {
		ISP_PATTERNS[key] = pattern;
	}
}

module.exports = ISPDetector;
