import * as dns from 'dns';
import { promisify } from 'util';

const dnsLookup = promisify(dns.lookup);

export interface NetworkErrorResult {
  isNetworkError: boolean;
  errorType?: 'connection' | 'dns' | 'timeout' | 'refused' | 'reset' | 'unknown';
  userMessage: string;
  technicalDetails?: string;
}

export class NetworkErrorDetector {
  private static NETWORK_ERROR_CODES = [
    'ENOTFOUND', // DNS resolution failed
    'ETIMEDOUT', // Connection timeout
    'ECONNREFUSED', // Connection refused
    'ECONNRESET', // Connection reset
    'ENETUNREACH', // Network unreachable
    'EHOSTUNREACH', // Host unreachable
    'EAI_AGAIN', // DNS temporary failure
  ];

  /**
   * Classify an error and determine if it's network-related
   * This is called ONLY when an error occurs (lazy validation)
   */
  static async classifyError(error: Error, serviceName: string = 'API'): Promise<NetworkErrorResult> {
    const errorMessage = error.message.toLowerCase();
    const errorCode = (error as any).code?.toUpperCase();

    // Check if it's a known network error
    if (this.isNetworkErrorCode(errorCode) || this.containsNetworkKeywords(errorMessage)) {
      // Perform a quick connectivity check to confirm
      const isOnline = await this.quickConnectivityCheck();

      if (!isOnline) {
        return {
          isNetworkError: true,
          errorType: 'connection',
          userMessage: `Network connection failed: Unable to reach ${serviceName}. Please check your internet connection.`,
          technicalDetails: error.message,
        };
      }

      // Network is up, but specific error occurred
      const specificError = this.getSpecificNetworkError(errorCode, errorMessage, serviceName);
      return specificError;
    }

    // Not a network error - return original error
    return {
      isNetworkError: false,
      userMessage: error.message,
      technicalDetails: error.message,
    };
  }

  /**
   * Quick connectivity check - only called when an error occurs
   * Tests DNS resolution to a reliable endpoint (Google DNS)
   */
  private static async quickConnectivityCheck(): Promise<boolean> {
    try {
      // Use Google's public DNS as a reliable test endpoint
      await Promise.race([
        dnsLookup('dns.google'),
        new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 2000)),
      ]);
      return true;
    } catch {
      return false;
    }
  }

  /**
   * Check if error code is a known network error
   */
  private static isNetworkErrorCode(code?: string): boolean {
    if (!code) return false;
    return this.NETWORK_ERROR_CODES.includes(code);
  }

  /**
   * Check if error message contains network-related keywords
   */
  private static containsNetworkKeywords(message: string): boolean {
    const keywords = [
      'fetch failed',
      'network',
      'connection',
      'timeout',
      'dns',
      'unreachable',
      'refused',
      'reset',
      'enotfound',
      'etimedout',
      'econnrefused',
      'econnreset',
    ];

    return keywords.some((keyword) => message.includes(keyword));
  }

  /**
   * Get specific error details based on error code/message
   */
  private static getSpecificNetworkError(
    code: string | undefined,
    message: string,
    serviceName: string,
  ): NetworkErrorResult {
    // DNS resolution failures
    if (code === 'ENOTFOUND' || code === 'EAI_AGAIN' || message.includes('dns')) {
      return {
        isNetworkError: true,
        errorType: 'dns',
        userMessage: `DNS resolution failed: Unable to resolve ${serviceName} hostname. Check your DNS settings or internet connection.`,
        technicalDetails: `Error code: ${code || 'unknown'}`,
      };
    }

    // Connection timeout
    if (code === 'ETIMEDOUT' || message.includes('timeout')) {
      return {
        isNetworkError: true,
        errorType: 'timeout',
        userMessage: `Connection timeout: ${serviceName} did not respond in time. This may be due to slow internet or firewall blocking.`,
        technicalDetails: `Error code: ${code || 'timeout'}`,
      };
    }

    // Connection refused
    if (code === 'ECONNREFUSED' || message.includes('refused')) {
      return {
        isNetworkError: true,
        errorType: 'refused',
        userMessage: `Connection refused: ${serviceName} is not accepting connections. Service may be down or blocked by firewall.`,
        technicalDetails: `Error code: ${code || 'refused'}`,
      };
    }

    // Connection reset
    if (code === 'ECONNRESET' || code === 'ENETUNREACH' || code === 'EHOSTUNREACH') {
      return {
        isNetworkError: true,
        errorType: 'reset',
        userMessage: `Connection lost: Network connection to ${serviceName} was interrupted. Check your internet stability.`,
        technicalDetails: `Error code: ${code || 'reset'}`,
      };
    }

    // Generic network error
    return {
      isNetworkError: true,
      errorType: 'unknown',
      userMessage: `Network error: Unable to connect to ${serviceName}. Please check your internet connection and firewall settings.`,
      technicalDetails: message,
    };
  }

  /**
   * Format error for logging with both user-friendly and technical details
   */
  static formatErrorForLogging(result: NetworkErrorResult): string {
    if (result.isNetworkError) {
      return `[NETWORK ERROR - ${result.errorType?.toUpperCase()}] ${result.userMessage}${
        result.technicalDetails ? ` | Technical: ${result.technicalDetails}` : ''
      }`;
    }
    return result.userMessage;
  }
}
