/**
 * IP Detection Service
 * Detects all network interfaces and classifies IPs for direct connection
 */ import os from 'os';
import fetch from 'node-fetch';
import { serverLogger } from '../utils/serverLogger.js';
export class IPDetectionService {
    cachedPublicIP = null;
    lastPublicIPCheck = 0;
    PUBLIC_IP_CACHE_TTL = 300000;
    /**
   * Detect all available IP addresses
   */ async detectAllIPs() {
        const ips = [];
        // Detect local network interfaces
        const interfaces = os.networkInterfaces();
        for (const [name, addrs] of Object.entries(interfaces)){
            if (!addrs) continue;
            for (const addr of addrs){
                // Skip internal loopback unless it's the only interface
                if (addr.internal && addr.address !== '127.0.0.1') {
                    continue;
                }
                // Focus on IPv4 for now (IPv6 support can be added later)
                if (addr.family === 'IPv4') {
                    const type = this.classifyIP(addr.address);
                    const priority = this.calculatePriority(type, name);
                    ips.push({
                        address: addr.address,
                        type,
                        interface: name,
                        priority,
                        family: 'IPv4'
                    });
                }
            }
        }
        // Detect public IP (if not behind NAT)
        const publicIP = await this.detectPublicIP();
        if (publicIP && !ips.some((ip)=>ip.address === publicIP)) {
            ips.push({
                address: publicIP,
                type: 'public',
                interface: 'external',
                priority: 100,
                family: 'IPv4'
            });
        }
        // Sort by priority (highest first)
        return ips.sort((a, b)=>b.priority - a.priority);
    }
    /**
   * Classify an IP address
   */ classifyIP(ip) {
        if (ip.startsWith('127.')) {
            return 'loopback';
        }
        // Private networks
        if (ip.startsWith('192.168.') || ip.startsWith('10.') || ip.match(/^172\.(1[6-9]|2[0-9]|3[01])\./)) {
            return 'private';
        }
        // Tailscale/Zerotier VPN ranges
        if (ip.startsWith('100.') || ip.startsWith('172.16.')) {
            return 'vpn';
        }
        return 'public';
    }
    /**
   * Calculate connection priority for an IP
   * Higher priority = try this connection first
   */ calculatePriority(type, interfaceName) {
        // Base priority by type
        const basePriority = {
            private: 80,
            vpn: 70,
            public: 50,
            loopback: 10
        };
        let priority = basePriority[type];
        // Boost priority for WiFi interfaces
        if (interfaceName.toLowerCase().includes('en0') || interfaceName.toLowerCase().includes('wi')) {
            priority += 10;
        }
        // Boost priority for Ethernet
        if (interfaceName.toLowerCase().includes('eth') || interfaceName.toLowerCase().includes('en1')) {
            priority += 15;
        }
        return priority;
    }
    /**
   * Detect public IP address
   */ async detectPublicIP() {
        // Check cache first
        const now = Date.now();
        if (this.cachedPublicIP && now - this.lastPublicIPCheck < this.PUBLIC_IP_CACHE_TTL) {
            return this.cachedPublicIP;
        }
        try {
            // Use multiple services for reliability
            const services = [
                {
                    url: 'https://api.ipify.org?format=json',
                    parser: (data)=>data.ip
                },
                {
                    url: 'https://api.my-ip.io/ip.json',
                    parser: (data)=>data.ip
                },
                {
                    url: 'https://icanhazip.com',
                    parser: (text)=>text.trim()
                }
            ];
            for (const service of services){
                try {
                    const controller = new AbortController();
                    const timeoutId = setTimeout(()=>controller.abort(), 3000);
                    const response = await fetch(service.url, {
                        signal: controller.signal
                    });
                    clearTimeout(timeoutId);
                    if (!response.ok) continue;
                    const contentType = response.headers.get('content-type');
                    let ip;
                    if (contentType?.includes('application/json')) {
                        const data = await response.json();
                        ip = service.parser(data);
                    } else {
                        const text = await response.text();
                        ip = service.parser(text);
                    }
                    if (this.isValidPublicIP(ip)) {
                        this.cachedPublicIP = ip;
                        this.lastPublicIPCheck = now;
                        serverLogger.info('[IPDetection] Detected public IP', {
                            ip
                        });
                        return ip;
                    }
                } catch (error) {
                    serverLogger.debug('[IPDetection] Failed to check service', {
                        url: service.url,
                        error: error instanceof Error ? error.message : 'Unknown error'
                    });
                    continue;
                }
            }
            serverLogger.warn('[IPDetection] Could not detect public IP from any service');
            return null;
        } catch (error) {
            serverLogger.error('[IPDetection] Error detecting public IP', {
                error
            });
            return null;
        }
    }
    /**
   * Validate that an IP is a valid public IPv4 address
   */ isValidPublicIP(ip) {
        // Basic IPv4 format check
        const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
        if (!ipv4Regex.test(ip)) {
            return false;
        }
        // Check each octet is valid (0-255)
        const octets = ip.split('.').map(Number);
        if (octets.some((octet)=>octet < 0 || octet > 255)) {
            return false;
        }
        // Exclude private and reserved ranges
        const type = this.classifyIP(ip);
        return type === 'public';
    }
    /**
   * Encode IP address for use in subdomain
   * Example: 192.168.1.100 -> 192-168-1-100
   */ encodeIP(ip) {
        return ip.replace(/\./g, '-');
    }
    /**
   * Decode IP address from subdomain
   * Example: 192-168-1-100 -> 192.168.1.100
   */ decodeIP(encoded) {
        // Remove any domain suffix if present
        const ipPart = encoded.split('.')[0];
        // Convert dashes back to dots
        const ip = ipPart.replace(/-/g, '.');
        // Validate it's a proper IP
        const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
        if (!ipv4Regex.test(ip)) {
            return null;
        }
        // Check each octet is valid (0-255)
        const octets = ip.split('.').map(Number);
        if (octets.some((octet)=>octet < 0 || octet > 255)) {
            return null;
        }
        return ip;
    }
    /**
   * Generate connection URL for an IP
   */ generateConnectionUrl(ip, port = 443, domain = 'sound.direct') {
        const encoded = this.encodeIP(ip);
        // For standard HTTPS port, omit it
        if (port === 443) {
            return `https://${encoded}.${domain}`;
        }
        // For custom ports, include in URL
        return `https://${encoded}.${domain}:${port}`;
    }
    /**
   * Get connection URLs in priority order
   */ async getConnectionUrls(port = 443, domain = 'sound.direct') {
        const ips = await this.detectAllIPs();
        return ips.map((ipInfo)=>this.generateConnectionUrl(ipInfo.address, port, domain));
    }
    /**
   * Get a summary of network status for UI display
   */ async getNetworkSummary() {
        const ips = await this.detectAllIPs();
        return {
            privateIPs: ips.filter((ip)=>ip.type === 'private').map((ip)=>ip.address),
            publicIP: ips.find((ip)=>ip.type === 'public')?.address || null,
            vpnIPs: ips.filter((ip)=>ip.type === 'vpn').map((ip)=>ip.address),
            connectionUrls: ips.map((ipInfo)=>this.generateConnectionUrl(ipInfo.address))
        };
    }
}
// Singleton instance
let ipDetectionService = null;
export function getIPDetectionService() {
    if (!ipDetectionService) {
        ipDetectionService = new IPDetectionService();
    }
    return ipDetectionService;
}

//# sourceMappingURL=ipDetectionService.js.map