/**
 * @fileoverview API Client - Unified HTTP Communication Layer
 * @version 1.0.0
 * @since 2025-08-04
 * @lastUpdated 2025-08-04
 * @module ApiClient
 * @description Centralized API client with authentication and error handling
 * @contributors Claude Code Agent
 * @dependencies AuthService
 * @requirements REQ-API-001 (Unified API client)
 * @testCoverage HTTP requests, authentication, error handling
 */

import { AuthService } from './AuthService';

/**
 * API Client
 *
 * @description Centralized HTTP client with automatic authentication,
 *              error handling, and request/response transformation.
 *
 * @example
 * ```typescript
 * const apiClient = ApiClient.getInstance();
 * const data = await apiClient.get('/api/users');
 * ```
 *
 * @since 2025-08-04
 * @author Claude Code Agent
 * @requirements REQ-API-001 (Unified API client), REQ-AUTH-002 (Authenticated requests)
 */
export class ApiClient {
  private static instance: ApiClient;
  private authService: AuthService;
  private baseURL: string;

  constructor(baseURL: string = '') {
    this.baseURL = baseURL;
    this.authService = AuthService.getInstance();
  }

  /**
   * Get singleton instance
   *
   * @description Returns singleton instance following established pattern
   *
   * @returns {ApiClient} Singleton instance
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  static getInstance(): ApiClient {
    if (!ApiClient.instance) {
      ApiClient.instance = new ApiClient();
    }
    return ApiClient.instance;
  }

  /**
   * GET request
   *
   * @description Performs authenticated GET request
   *
   * @param {string} url - Request URL
   * @param {RequestConfig} config - Optional request configuration
   * @returns {Promise<ApiResponse>} Response data
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  async get(url: string, config: RequestConfig = {}): Promise<ApiResponse> {
    return this.request(url, { ...config, method: 'GET' });
  }

  /**
   * POST request
   *
   * @description Performs authenticated POST request
   *
   * @param {string} url - Request URL
   * @param {any} data - Request body data
   * @param {RequestConfig} config - Optional request configuration
   * @returns {Promise<ApiResponse>} Response data
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  async post(url: string, data?: any, config: RequestConfig = {}): Promise<ApiResponse> {
    return this.request(url, { ...config, method: 'POST', data });
  }

  /**
   * PUT request
   *
   * @description Performs authenticated PUT request
   *
   * @param {string} url - Request URL
   * @param {any} data - Request body data
   * @param {RequestConfig} config - Optional request configuration
   * @returns {Promise<ApiResponse>} Response data
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  async put(url: string, data?: any, config: RequestConfig = {}): Promise<ApiResponse> {
    return this.request(url, { ...config, method: 'PUT', data });
  }

  /**
   * DELETE request
   *
   * @description Performs authenticated DELETE request
   *
   * @param {string} url - Request URL
   * @param {RequestConfig} config - Optional request configuration
   * @returns {Promise<ApiResponse>} Response data
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  async delete(url: string, config: RequestConfig = {}): Promise<ApiResponse> {
    return this.request(url, { ...config, method: 'DELETE' });
  }

  /**
   * PATCH request
   *
   * @description Performs authenticated PATCH request
   *
   * @param {string} url - Request URL
   * @param {any} data - Request body data
   * @param {RequestConfig} config - Optional request configuration
   * @returns {Promise<ApiResponse>} Response data
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  async patch(url: string, data?: any, config: RequestConfig = {}): Promise<ApiResponse> {
    return this.request(url, { ...config, method: 'PATCH', data });
  }

  /**
   * Unified request method
   *
   * @description Core request method with authentication and error handling
   *
   * @param {string} url - Request URL
   * @param {RequestConfig} config - Request configuration
   * @returns {Promise<ApiResponse>} Response data
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  private async request(url: string, config: RequestConfig): Promise<ApiResponse> {
    const fullURL = this.buildURL(url);
    const requestConfig = await this.buildRequestConfig(config);

    try {
      const response = await fetch(fullURL, requestConfig);
      return await this.handleResponse(response);
    } catch (error) {
      throw this.handleError(error);
    }
  }

  /**
   * Build full URL
   *
   * @description Constructs full URL from base URL and path
   *
   * @param {string} url - Request path
   * @returns {string} Full URL
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  private buildURL(url: string): string {
    if (url.startsWith('http://') || url.startsWith('https://')) {
      return url;
    }

    const base = this.baseURL || window.location.origin;
    const cleanBase = base.replace(/\/$/, '');
    const cleanPath = url.replace(/^\//, '');

    return `${cleanBase}/${cleanPath}`;
  }

  /**
   * Build request configuration
   *
   * @description Prepares fetch configuration with authentication
   *
   * @param {RequestConfig} config - Input configuration
   * @returns {Promise<RequestInit>} Fetch configuration
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  private async buildRequestConfig(config: RequestConfig): Promise<RequestInit> {
    const headers = new Headers(config.headers);

    // Add authentication
    if (!config.skipAuth) {
      const authHeader = this.authService.getAuthHeader();
      if (authHeader) {
        headers.set('Authorization', authHeader);
      }
    }

    // Add content type for data requests
    if (config.data && !headers.has('Content-Type')) {
      headers.set('Content-Type', 'application/json');
    }

    const requestConfig: RequestInit = {
      method: config.method || 'GET',
      headers,
      credentials: 'include', // Include cookies for hybrid auth
    };

    // Add body for data requests
    if (config.data) {
      if (config.data instanceof FormData) {
        requestConfig.body = config.data;
        headers.delete('Content-Type'); // Let browser set multipart boundary
      } else {
        requestConfig.body = JSON.stringify(config.data);
      }
    }

    // Add signal for request cancellation
    if (config.signal) {
      requestConfig.signal = config.signal;
    }

    return requestConfig;
  }

  /**
   * Handle response
   *
   * @description Processes fetch response and handles errors
   *
   * @param {Response} response - Fetch response
   * @returns {Promise<ApiResponse>} Processed response
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  private async handleResponse(response: Response): Promise<ApiResponse> {
    const contentType = response.headers.get('Content-Type') || '';
    const isJSON = contentType.includes('application/json');

    let data: any;
    try {
      if (isJSON) {
        data = await response.json();
      } else {
        data = await response.text();
      }
    } catch (error) {
      data = null;
    }

    // Handle authentication errors
    if (response.status === 401) {
      await this.handleUnauthorized();
      throw new ApiError('Authentication required', 401, data);
    }

    // Handle other HTTP errors
    if (!response.ok) {
      const message = this.extractErrorMessage(data);
      throw new ApiError(message, response.status, data);
    }

    return {
      data,
      status: response.status,
      statusText: response.statusText,
      headers: this.responseHeadersToObject(response.headers),
    };
  }

  /**
   * Handle unauthorized response
   *
   * @description Attempts token refresh or redirects to login
   *
   * @since 2025-08-04
   * @author Copilot/Jorge
   */
  private async handleUnauthorized(): Promise<void> {
    try {
      // Attempt to refresh token
      await this.authService.refreshToken();
    } catch (error) {
      // Refresh failed, redirect to login
      await this.authService.logout();

      // Only redirect if not already on auth page
      if (!window.location.pathname.includes('/auth/')) {
        window.location.href = '/auth/login.html';
      }
    }
  }

  /**
   * Extract error message from response
   *
   * @description Extracts user-friendly error message from API response
   *
   * @param {any} data - Response data
   * @returns {string} Error message
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  private extractErrorMessage(data: any): string {
    if (!data) return 'Request failed';

    // Handle different error formats
    if (typeof data === 'string') {
      return data;
    }

    if (data.error) {
      if (typeof data.error === 'string') {
        return data.error;
      }
      if (data.error.message) {
        return data.error.message;
      }
    }

    if (data.message) {
      return data.message;
    }

    return 'Request failed';
  }

  /**
   * Handle request error
   *
   * @description Processes request errors (network, etc.)
   *
   * @param {any} error - Error object
   * @returns {ApiError} Processed error
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  private handleError(error: any): ApiError {
    if (error instanceof ApiError) {
      return error;
    }

    if (error.name === 'AbortError') {
      return new ApiError('Request cancelled', 0, error);
    }

    if (error.name === 'TypeError' && error.message.includes('fetch')) {
      return new ApiError('Network error. Please check your connection.', 0, error);
    }

    return new ApiError(error.message || 'An unexpected error occurred', 0, error);
  }

  /**
   * Convert response headers to object
   *
   * @description Converts Headers object to plain object
   *
   * @param {Headers} headers - Response headers
   * @returns {Record<string, string>} Headers object
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  private responseHeadersToObject(headers: Headers): Record<string, string> {
    const result: Record<string, string> = {};
    headers.forEach((value, key) => {
      result[key] = value;
    });
    return result;
  }

  /**
   * Create request with timeout
   *
   * @description Creates request with automatic timeout
   *
   * @param {string} url - Request URL
   * @param {RequestConfig} config - Request configuration
   * @param {number} timeout - Timeout in milliseconds
   * @returns {Promise<ApiResponse>} Response data
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  async requestWithTimeout(
    url: string,
    config: RequestConfig,
    timeout: number = 30000
  ): Promise<ApiResponse> {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeout);

    try {
      const response = await this.request(url, {
        ...config,
        signal: controller.signal,
      });
      clearTimeout(timeoutId);
      return response;
    } catch (error) {
      clearTimeout(timeoutId);
      throw error;
    }
  }

  /**
   * Upload file
   *
   * @description Uploads file with progress tracking
   *
   * @param {string} url - Upload URL
   * @param {File} file - File to upload
   * @param {UploadConfig} config - Upload configuration
   * @returns {Promise<ApiResponse>} Upload response
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  async uploadFile(url: string, file: File, config: UploadConfig = {}): Promise<ApiResponse> {
    const formData = new FormData();
    formData.append(config.fieldName || 'file', file);

    // Add additional fields
    if (config.fields) {
      Object.entries(config.fields).forEach(([key, value]) => {
        formData.append(key, value);
      });
    }

    return this.post(url, formData, {
      skipAuth: config.skipAuth,
      headers: config.headers,
    });
  }

  /**
   * Set base URL
   *
   * @description Updates the base URL for all requests
   *
   * @param {string} baseURL - New base URL
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  setBaseURL(baseURL: string): void {
    this.baseURL = baseURL;
  }

  /**
   * Get base URL
   *
   * @description Returns current base URL
   *
   * @returns {string} Current base URL
   *
   * @since 2025-08-04
   * @author Claude Code Agent
   */
  getBaseURL(): string {
    return this.baseURL;
  }
}

/**
 * API Error Class
 *
 * @description Custom error class for API-related errors
 *
 * @since 2025-08-04
 * @author Claude Code Agent
 */
export class ApiError extends Error {
  public status: number;
  public data: any;

  constructor(message: string, status: number = 0, data: any = null) {
    super(message);
    this.name = 'ApiError';
    this.status = status;
    this.data = data;
  }
}

/**
 * Type Definitions
 */

export interface RequestConfig {
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
  headers?: Record<string, string>;
  data?: any;
  skipAuth?: boolean;
  signal?: AbortSignal;
}

export interface ApiResponse {
  data: any;
  status: number;
  statusText: string;
  headers: Record<string, string>;
}

export interface UploadConfig {
  fieldName?: string;
  fields?: Record<string, string>;
  skipAuth?: boolean;
  headers?: Record<string, string>;
  onProgress?: (progress: number) => void;
}
