import { ErrorHandler } from './errorHandler';
import { log } from './logger';
import { certificatePinning } from '../services/CertificatePinning';
import { API_CONFIG } from '../config/api';

interface RetryConfig {
  maxRetries: number;
  baseDelay: number;
  maxDelay: number;
  retryCondition?: (error: unknown) => boolean;
}

interface RequestConfig extends RequestInit {
  timeout?: number;
  retryConfig?: Partial<RetryConfig>;
}

export class ApiClient {
  private static defaultRetryConfig: RetryConfig = {
    maxRetries: 3,
    baseDelay: 1000,
    maxDelay: 10000,
    retryCondition: (error) => {
      if (error instanceof Response) {
        // Retry on server errors and rate limiting
        return error.status >= 500 || error.status === 429;
      }
      // Retry on network errors
      return error instanceof TypeError && error.message.includes('fetch');
    }
  };

  private static defaultTimeout = 30000; // 30 seconds
  
  // CSRF token removed - not needed for JWT authentication
  // JWT Bearer tokens are not vulnerable to CSRF attacks

  /**
   * Execute HTTP request with retry logic and timeout
   */
  static async fetchWithRetry<T>(
    url: string,
    options: RequestConfig = {}
  ): Promise<T> {
    const {
      timeout = this.defaultTimeout,
      retryConfig = {},
      ...fetchOptions
    } = options;

    // Add Authorization token if not explicitly set
    if (!(fetchOptions.headers as Record<string, string>)?.['Authorization']) {
      try {
        const authService = (await import('../services/auth-service')).authService;
        const token = authService.getToken();
        if (token) {
          fetchOptions.headers = {
            ...fetchOptions.headers,
            'Authorization': `Bearer ${token}`
          };
        }
      } catch (error) {
        // Ignore errors when getting token
      }
    }

    // CSRF token removed - not needed for JWT API

    // Certificate pinning check for production
    if (process.env.NODE_ENV === 'production') {
      try {
        const hostname = new URL(url).hostname;
        
        // Load dynamic pins if cache is invalid
        if (!(certificatePinning as any).isPinCacheValid()) {
          try {
            await (certificatePinning as any).loadDynamicPins(API_CONFIG.BASE_URL);
          } catch (error) {
            log.warn('api-client', 'Failed to load dynamic certificate pins, using static pins', { error });
          }
        }
        
        const isValid = await (certificatePinning as any).verifyCertificate(hostname);
        if (!isValid) {
          const error = new Error(`Certificate pinning validation failed for ${hostname}`);
          log.error('api-client', 'Certificate pinning failed', { hostname, url });
          throw error;
        }
        log.debug('api-client', 'Certificate pinning validation passed', { hostname });
      } catch (error) {
        log.error('api-client', 'Certificate pinning error', { error, url });
        throw error;
      }
    }

    const config = { ...this.defaultRetryConfig, ...retryConfig };
    let lastError: unknown;
    let attempt = 0;

    while (attempt <= config.maxRetries) {
      try {
        log.debug('api-client', `Attempting request (${attempt + 1}/${config.maxRetries + 1})`, {
          url,
          method: fetchOptions.method || 'GET',
          attempt: attempt + 1
        });

        // Create AbortController for timeout
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), timeout);

        const response = await fetch(url, {
          ...fetchOptions,
          signal: controller.signal
        });

        clearTimeout(timeoutId);

        if (!response.ok) {
          const error = response.clone();
          
          // Check if request should be retried
          if (attempt < config.maxRetries && config.retryCondition?.(error)) {
            lastError = error;
            const delay = this.calculateDelay(attempt, config);
            
            log.warn('api-client', `Request failed, retrying in ${delay}ms`, {
              url,
              status: response.status,
              attempt: attempt + 1,
              nextRetryIn: delay
            });

            await this.delay(delay);
            attempt++;
            continue;
          }
          
          throw error;
        }

        // Successful response
        log.info('api-client', 'Request successful', {
          url,
          status: response.status,
          attempt: attempt + 1
        });

        // Try parsing JSON if content exists
        const contentType = response.headers.get('content-type');
        if (contentType?.includes('application/json')) {
          return await response.json();
        }
        
        // Return empty object for successful requests without JSON
        return {} as T;

      } catch (error) {
        lastError = error;
        
        // Check if request should be retried
        if (attempt < config.maxRetries && config.retryCondition?.(error)) {
          const delay = this.calculateDelay(attempt, config);
          
          log.warn('api-client', `Request failed with error, retrying in ${delay}ms`, {
            url,
            error: error instanceof Error ? error.message : String(error),
            attempt: attempt + 1,
            nextRetryIn: delay
          });

          await this.delay(delay);
          attempt++;
          continue;
        }
        
        // Maximum retry attempts exhausted
        break;
      }
    }

    // Handle final error
    log.error('api-client', 'Request failed after all retries', {
      url,
      totalAttempts: attempt + 1,
      error: lastError
    });

    const appError = await ErrorHandler.handleApiError(lastError);
    throw appError;
  }

  /**
   * GET request
   */
  static async get<T>(url: string, options: RequestConfig = {}): Promise<T> {
    return this.fetchWithRetry<T>(url, {
      ...options,
      method: 'GET'
    });
  }

  /**
   * POST request
   */
  static async post<T>(
    url: string, 
    body?: unknown, 
    options: RequestConfig = {}
  ): Promise<T> {
    return this.fetchWithRetry<T>(url, {
      ...options,
      method: 'POST',
      body: body ? JSON.stringify(body) : undefined,
      headers: {
        'Content-Type': 'application/json',
        ...options.headers
      }
    });
  }

  /**
   * PUT request
   */
  static async put<T>(
    url: string, 
    body?: unknown, 
    options: RequestConfig = {}
  ): Promise<T> {
    return this.fetchWithRetry<T>(url, {
      ...options,
      method: 'PUT',
      body: body ? JSON.stringify(body) : undefined,
      headers: {
        'Content-Type': 'application/json',
        ...options.headers
      }
    });
  }

  /**
   * DELETE request
   */
  static async delete<T>(url: string, options: RequestConfig = {}): Promise<T> {
    return this.fetchWithRetry<T>(url, {
      ...options,
      method: 'DELETE'
    });
  }

  /**
   * Calculate retry delay with exponential backoff and jitter
   */
  private static calculateDelay(attempt: number, config: RetryConfig): number {
    // Exponential backoff: baseDelay * 2^attempt
    const exponentialDelay = config.baseDelay * Math.pow(2, attempt);
    
    // Add jitter (randomness) up to 10% of delay
    const jitter = Math.random() * 0.1 * exponentialDelay;
    
    // Apply maximum delay
    return Math.min(exponentialDelay + jitter, config.maxDelay);
  }

  /**
   * Delay
   */
  private static delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  /**
   * Create request configuration with authorization
   */
  static withAuth(token: string, options: RequestConfig = {}): RequestConfig {
    return {
      ...options,
      headers: {
        'Authorization': `Bearer ${token}`,
        ...options.headers
      }
    };
  }

  /**
   * Check network status (API availability)
   */
  static async checkHealth(baseUrl: string): Promise<boolean> {
    try {
      await this.get(`${baseUrl}/health`, {
        timeout: 5000,
        retryConfig: { maxRetries: 1 }
      });
      return true;
    } catch (error) {
      log.warn('api-client', 'Health check failed', { error });
      return false;
    }
  }
} 