/**
 * Base API Client for HTTP operations
 *
 * Provides generic HTTP methods with common request/response handling,
 * error handling, authentication, retry logic, and circuit breaker.
 */

import { Logger } from '../../interfaces/Logger';
import { ErrorReporter } from '../../interfaces/ErrorReporter';
import { ApiError, ApiErrorType } from './ApiError';
import { RetryHandler, CircuitBreakerRegistry, DEFAULT_RETRY_CONFIG } from './RetryHandler';
import { ApiConnectionMonitor } from './ApiConnectionMonitor';
import { getGlobalApiLogger } from '@/lib/infrastructure/ApiRequestLogger';

export interface BaseApiClientOptions {
  timeout?: number;
  retry?: boolean;
  retryConfig?: typeof DEFAULT_RETRY_CONFIG;
  allowUnauthenticated?: boolean;
}

export class BaseApiClient {
  protected baseUrl: string;
  private errorReporter: ErrorReporter;
  private logger: Logger;
  private retryHandler: RetryHandler;
  private circuitBreakerRegistry: CircuitBreakerRegistry;
  private connectionMonitor: ApiConnectionMonitor;
  private defaultOptions: BaseApiClientOptions;

  constructor(
    baseUrl: string,
    errorReporter: ErrorReporter,
    logger: Logger,
    options: BaseApiClientOptions = {}
  ) {
    this.baseUrl = baseUrl;
    this.errorReporter = errorReporter;
    this.logger = logger;
    this.retryHandler = new RetryHandler(options.retryConfig || DEFAULT_RETRY_CONFIG);
    this.circuitBreakerRegistry = CircuitBreakerRegistry.getInstance();
    this.connectionMonitor = ApiConnectionMonitor.getInstance();
    this.defaultOptions = {
      timeout: options.timeout || 30000,
      retry: options.retry !== false,
      retryConfig: options.retryConfig || DEFAULT_RETRY_CONFIG,
    };

    // Start monitoring connection health
    // this.connectionMonitor.startMonitoring();
  }

  /**
   * Classify HTTP status code into error type
   */
  private classifyError(status: number): ApiErrorType {
    if (status >= 500) return 'SERVER_ERROR';
    if (status === 429) return 'RATE_LIMIT_ERROR';
    if (status === 401 || status === 403) return 'AUTH_ERROR';
    if (status === 400) return 'VALIDATION_ERROR';
    if (status === 404) return 'NOT_FOUND';
    return 'UNKNOWN_ERROR';
  }

  /**
   * Create an ApiError from fetch response
   */
  private async createApiError(
    response: Response,
    method: string,
    path: string,
    retryCount: number = 0
  ): Promise<ApiError> {
    const status = response.status;
    const errorType = this.classifyError(status);
    
    let message = response.statusText;
    let responseText = '';

    try {
      responseText = await response.text();
      if (responseText) {
        const errorData = JSON.parse(responseText);
        if (errorData.message) {
          message = errorData.message;
        }
      }
    } catch {
      // Keep default message
    }

    return new ApiError(
      message,
      errorType,
      {
        endpoint: path,
        method,
        statusCode: status,
        responseText,
        timestamp: new Date().toISOString(),
        retryCount,
      }
    );
  }

  /**
   * Create an ApiError from network/timeout errors
   */
  private createNetworkError(
    error: Error,
    method: string,
    path: string,
    retryCount: number = 0
  ): ApiError {
    let errorType: ApiErrorType = 'NETWORK_ERROR';
    let message = error.message;

    // More specific error classification
    if (error.name === 'AbortError') {
      errorType = 'CANCELED_ERROR';
      message = 'Request was canceled';
    } else if (error.name === 'TypeError' && error.message.includes('fetch')) {
      errorType = 'NETWORK_ERROR';
      // Check for CORS specifically
      if (error.message.includes('Failed to fetch') || error.message.includes('fetch failed')) {
        message = 'Unable to connect to server. Possible CORS or network issue.';
      }
    } else if (error.message.includes('timeout') || error.message.includes('timed out')) {
      errorType = 'TIMEOUT_ERROR';
      message = 'Request timed out after 30 seconds';
    } else if (error.message.includes('Failed to fetch') || error.message.includes('NetworkError')) {
      errorType = 'NETWORK_ERROR';
      // This could be CORS, network down, or server not responding
      message = 'Network error: Unable to reach the API server';
    }

    return new ApiError(
      message,
      errorType,
      {
        endpoint: path,
        method,
        timestamp: new Date().toISOString(),
        retryCount,
        // Add helpful context for developers
        troubleshooting: this.getTroubleshootingContext(error, path),
        isRetryable: this.isRetryableError(errorType),
        isConnectivity: errorType === 'NETWORK_ERROR' || errorType === 'TIMEOUT_ERROR',
        developerHint: this.getDeveloperHint(error, path, method),
      },
      error
    );
  }

  /**
   * Check if error type is retryable
   */
  private isRetryableError(errorType: ApiErrorType): boolean {
    const retryableTypes: ApiErrorType[] = [
      'NETWORK_ERROR',
      'SERVER_ERROR',
      'RATE_LIMIT_ERROR',
      'TIMEOUT_ERROR',
    ];
    return retryableTypes.includes(errorType);
  }

  /**
   * Get developer-friendly hint for troubleshooting
   */
  private getDeveloperHint(error: Error, _path: string, _method: string): string {
    if (error.message.includes('fetch failed') || error.message.includes('Failed to fetch')) {
      return 'Check if API server is running and CORS is configured correctly';
    }
    if (error.message.includes('timeout')) {
      return 'Request timed out - consider increasing timeout or checking network';
    }
    if (error.message.includes('ECONNREFUSED')) {
      return 'Connection refused - verify API server address and port';
    }
    return 'Review network connection and API endpoint configuration';
  }

  /**
   * Get troubleshooting context for network errors
   */
  private getTroubleshootingContext(error: Error, _path: string): string {
    if (typeof window !== 'undefined') {
      const baseUrl = this.baseUrl;
      const currentOrigin = window.location.origin;
      
      // Check if it's likely a CORS issue
      if (baseUrl && !baseUrl.includes(currentOrigin) && error.message.includes('Failed to fetch')) {
        return 'CORS issue likely. Check API server CORS configuration.';
      }
      
      // Check if API server is same origin
      if (baseUrl.includes(currentOrigin) || baseUrl.startsWith('/')) {
        return 'Same-origin request. Check if API server is running.';
      }
    }
    
    return 'Check network connection and API server status.';
  }

  protected async request<T>(
    method: string,
    path: string,
    data?: object | FormData,
    options: BaseApiClientOptions & { allowUnauthenticated?: boolean } = {},
  ): Promise<T> {
    const finalOptions = { ...this.defaultOptions, ...options };
    const endpoint = `${this.baseUrl}${path}`;

    // Check circuit breaker
    const circuitBreaker = this.circuitBreakerRegistry.getBreaker(path);
    if (!circuitBreaker.canExecute()) {
      const error = new ApiError(
        'Circuit breaker is open - service temporarily unavailable',
        'SERVER_ERROR',
        {
          endpoint: path,
          method,
          timestamp: new Date().toISOString(),
        }
      );
      this.handleError(error);
      throw error;
    }

    const executeRequest = async (signal: AbortSignal): Promise<T> => {
      const isFormData = typeof FormData !== 'undefined' && data instanceof FormData;
      const headers: Record<string, string> = isFormData
        ? {}
        : {
            'Content-Type': 'application/json',
          };

      // Forward cookies if running on server
      if (typeof window === 'undefined') {
        try {
          const { cookies } = await import('next/headers');
          const cookieStore = await cookies();
          const cookieString = cookieStore.toString();
          if (cookieString) {
            headers['Cookie'] = cookieString;
          }
        } catch (e) {
          // Not in a request context or next/headers not available
        }
      }

      const config: RequestInit = {
        method,
        headers,
        credentials: 'include',
        signal,
      };

      if (data) {
        config.body = isFormData ? data : JSON.stringify(data);
      }

      const startTime = Date.now();
      let requestId: string | undefined;

      // Log request start (only in development for maximum transparency)
      if (process.env.NODE_ENV === 'development') {
        try {
          const apiLogger = getGlobalApiLogger();
          const headerObj: Record<string, string> = {};
          if (typeof headers === 'object') {
            Object.entries(headers).forEach(([key, value]) => {
              headerObj[key] = value;
            });
          }
          requestId = apiLogger.logRequest(
            endpoint,
            method,
            headerObj,
            data
          );
        } catch (e) {
          // Silent fail - logger might not be initialized
        }
      }

      try {
        const response = await fetch(endpoint, config);
        const responseTime = Date.now() - startTime;

        // Record success for monitoring
        this.connectionMonitor.recordSuccess(responseTime);

        if (!response.ok) {
          if (
            finalOptions.allowUnauthenticated &&
            (response.status === 401 || response.status === 403)
          ) {
            // For auth probe endpoints, 401/403 is expected
            return null as T;
          }

          const error = await this.createApiError(response, method, path);
          circuitBreaker.recordFailure();
          this.connectionMonitor.recordFailure(error);
          this.handleError(error);

          // Log error
          if (process.env.NODE_ENV === 'development' && requestId) {
            try {
              const apiLogger = getGlobalApiLogger();
              apiLogger.logError(requestId, error, responseTime);
            } catch (e) {
              // Silent fail
            }
          }

          throw error;
        }

        // Record successful circuit breaker call
        circuitBreaker.recordSuccess();

        const text = await response.text();
        if (!text) {
          // Log empty response
          if (process.env.NODE_ENV === 'development' && requestId) {
            try {
              const apiLogger = getGlobalApiLogger();
              apiLogger.logResponse(requestId, response, null, responseTime);
            } catch (e) {
              // Silent fail
            }
          }
          return null as T;
        }

        const parsedData = JSON.parse(text) as T;

        // Log successful response
        if (process.env.NODE_ENV === 'development' && requestId) {
          try {
            const apiLogger = getGlobalApiLogger();
            apiLogger.logResponse(requestId, response, parsedData, responseTime);
          } catch (e) {
            // Silent fail
          }
        }

        return parsedData;

      } catch (error) {
        const responseTime = Date.now() - startTime;

        if (error instanceof ApiError) {
          throw error;
        }

        // Convert to ApiError
        const apiError = this.createNetworkError(error as Error, method, path);
        
        circuitBreaker.recordFailure();
        this.connectionMonitor.recordFailure(apiError);
        this.handleError(apiError);

        // Log network error
        if (process.env.NODE_ENV === 'development' && requestId) {
          try {
            const apiLogger = getGlobalApiLogger();
            apiLogger.logError(requestId, apiError, responseTime);
          } catch (e) {
            // Silent fail
          }
        }
        
        throw apiError;
      }
    };

    // Wrap with retry logic if enabled
    if (finalOptions.retry) {
      try {
        return await this.retryHandler.execute(executeRequest);
      } catch (error) {
        // If retry exhausted, throw the final error
        throw error;
      }
    } else {
      // No retry, just execute with timeout
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), finalOptions.timeout);

      try {
        return await executeRequest(controller.signal);
      } finally {
        clearTimeout(timeoutId);
      }
    }
  }

  /**
   * Handle errors - log and report
   */
  private handleError(error: ApiError): void {
    const severity = error.getSeverity();
    const message = error.getDeveloperMessage();

    // Enhanced context for better debugging
    const enhancedContext = {
      ...error.context,
      severity,
      isRetryable: error.isRetryable(),
      isConnectivity: error.isConnectivityIssue(),
    };

    // Use appropriate log level
    if (severity === 'error') {
      this.logger.error(message, error, enhancedContext);
    } else if (severity === 'warn') {
      this.logger.warn(message, enhancedContext);
    } else {
      this.logger.info(message, enhancedContext);
    }

    // Report to error tracking
    this.errorReporter.report(error, enhancedContext);
  }

  protected get<T>(path: string, options?: BaseApiClientOptions): Promise<T> {
    return this.request<T>('GET', path, undefined, options);
  }

  protected post<T>(path: string, data: object, options?: BaseApiClientOptions): Promise<T> {
    return this.request<T>('POST', path, data, options);
  }

  protected put<T>(path: string, data: object, options?: BaseApiClientOptions): Promise<T> {
    return this.request<T>('PUT', path, data, options);
  }

  protected delete<T>(path: string, options?: BaseApiClientOptions): Promise<T> {
    return this.request<T>('DELETE', path, undefined, options);
  }

  protected patch<T>(path: string, data: object, options?: BaseApiClientOptions): Promise<T> {
    return this.request<T>('PATCH', path, data, options);
  }

  /**
   * Get current connection health status
   */
  getConnectionStatus() {
    return {
      status: this.connectionMonitor.getStatus(),
      health: this.connectionMonitor.getHealth(),
      isAvailable: this.connectionMonitor.isAvailable(),
      reliability: this.connectionMonitor.getReliability(),
    };
  }

  /**
   * Force a health check
   */
  async checkHealth() {
    return this.connectionMonitor.performHealthCheck();
  }

  /**
   * Get circuit breaker status for debugging
   */
  getCircuitBreakerStatus() {
    return this.circuitBreakerRegistry.getStatus();
  }
}
