import { getLogger } from './logging.js';

/**
 * Service configuration for IP detection
 */
interface IpService {
  name: string;
  v4Url?: string;
  v6Url?: string;
  parseResponse?: (text: string) => string;
}

/**
 * PublicIp class for detecting public IPv4 and IPv6 addresses
 * Uses multiple fallback services for reliability
 */
export class PublicIp {
  private readonly services: IpService[] = [
    {
      name: 'ipify',
      v4Url: 'https://api.ipify.org?format=text',
      v6Url: 'https://api6.ipify.org?format=text',
    },
    {
      name: 'ident.me',
      v4Url: 'https://v4.ident.me',
      v6Url: 'https://v6.ident.me',
    },
    {
      name: 'seeip',
      v4Url: 'https://ipv4.seeip.org',
      v6Url: 'https://ipv6.seeip.org',
    },
    {
      name: 'icanhazip',
      v4Url: 'https://ipv4.icanhazip.com',
      v6Url: 'https://ipv6.icanhazip.com',
    },
  ];

  private readonly timeout: number;
  private readonly logger = getLogger();

  constructor(options?: { timeout?: number }) {
    this.timeout = options?.timeout ?? 2000;
  }

  /**
   * Get public IPv4 address
   */
  public async getPublicIpv4(): Promise<string | null> {
    for (const service of this.services) {
      if (!service.v4Url) continue;
      
      try {
        const ip = await this.fetchIpFromService(service.v4Url, service.parseResponse);
        if (this.isValidIpv4(ip)) {
          this.logger.info?.(`Got IPv4 from ${service.name}: ${ip}`);
          return ip;
        }
      } catch (error: any) {
        this.logger.debug?.(`Failed to get IPv4 from ${service.name}: ${error.message}`);
      }
    }
    
    this.logger.warn?.('Could not determine public IPv4 address from any service');
    return null;
  }

  /**
   * Get public IPv6 address
   */
  public async getPublicIpv6(): Promise<string | null> {
    for (const service of this.services) {
      if (!service.v6Url) continue;
      
      try {
        const ip = await this.fetchIpFromService(service.v6Url, service.parseResponse);
        if (this.isValidIpv6(ip)) {
          this.logger.info?.(`Got IPv6 from ${service.name}: ${ip}`);
          return ip;
        }
      } catch (error: any) {
        this.logger.debug?.(`Failed to get IPv6 from ${service.name}: ${error.message}`);
      }
    }
    
    this.logger.warn?.('Could not determine public IPv6 address from any service');
    return null;
  }

  /**
   * Get both IPv4 and IPv6 addresses
   */
  public async getPublicIps(): Promise<{ v4: string | null; v6: string | null }> {
    const [v4, v6] = await Promise.all([
      this.getPublicIpv4(),
      this.getPublicIpv6(),
    ]);
    
    return { v4, v6 };
  }

  /**
   * Fetch IP from a service URL
   */
  private async fetchIpFromService(
    url: string,
    parseResponse?: (text: string) => string
  ): Promise<string> {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), this.timeout);
    
    try {
      const response = await fetch(url, {
        signal: controller.signal,
        headers: {
          'User-Agent': '@push.rocks/smartnetwork',
        },
      });
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      
      const text = await response.text();
      const ip = parseResponse ? parseResponse(text) : text.trim();
      
      return ip;
    } finally {
      clearTimeout(timeoutId);
    }
  }

  /**
   * Validate IPv4 address format
   */
  private isValidIpv4(ip: string): boolean {
    if (!ip) return false;
    
    const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
    if (!ipv4Regex.test(ip)) return false;
    
    const parts = ip.split('.');
    return parts.every(part => {
      const num = parseInt(part, 10);
      return num >= 0 && num <= 255;
    });
  }

  /**
   * Validate IPv6 address format
   */
  private isValidIpv6(ip: string): boolean {
    if (!ip) return false;
    
    // Simplified IPv6 validation - checks for colon-separated hex values
    const ipv6Regex = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/;
    const ipv6CompressedRegex = /^::([0-9a-fA-F]{0,4}:){0,6}[0-9a-fA-F]{0,4}$|^([0-9a-fA-F]{0,4}:){1,7}:$/;
    
    return ipv6Regex.test(ip) || ipv6CompressedRegex.test(ip);
  }
}