import type { ApiConfig, ErrorResponse } from '../types/common.js';

/**
 * HTTP response wrapper
 */
export interface HttpResponse<T> {
  data: T;
  status: number;
  statusText: string;
  headers: Headers;
}

/**
 * HTTP client error
 */
export class HttpClientError extends Error {
  constructor(
    message: string,
    public status: number,
    public response?: any
  ) {
    super(message);
    this.name = 'HttpClientError';
  }
}

/**
 * Base HTTP client for BODS API requests
 */
export class HttpClient {
  private readonly apiKey: string;
  private readonly baseUrl: string;
  private readonly timeout: number;

  constructor(config: ApiConfig) {
    this.apiKey = config.apiKey;
    this.baseUrl = config.baseUrl || 'https://data.bus-data.dft.gov.uk';
    this.timeout = config.timeout || 30000;
  }

  /**
   * Build URL with query parameters
   */
  private buildUrl(endpoint: string, params: Record<string, any> = {}): string {
    const url = new URL(endpoint, this.baseUrl);
    
    // Add API key
    url.searchParams.set('api_key', this.apiKey);
    
    // Add other parameters
    Object.entries(params).forEach(([key, value]) => {
      if (value !== undefined && value !== null) {
        if (Array.isArray(value)) {
          // Handle array parameters (comma-separated)
          url.searchParams.set(key, value.join(','));
        } else if (value instanceof Date) {
          // Handle date parameters
          url.searchParams.set(key, value.toISOString());
        } else {
          url.searchParams.set(key, String(value));
        }
      }
    });
    
    return url.toString();
  }

  /**
   * Make HTTP request with timeout and error handling
   */
  private async request<T>(
    endpoint: string,
    options: RequestInit & { params?: Record<string, any> } = {}
  ): Promise<HttpResponse<T>> {
    const { params, ...fetchOptions } = options;
    const url = this.buildUrl(endpoint, params);
    
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), this.timeout);
    
    try {
      const response = await fetch(url, {
        ...fetchOptions,
        signal: controller.signal,
        headers: {
          'Accept': 'application/json',
          ...fetchOptions.headers,
        },
      });
      
      clearTimeout(timeoutId);
      
      if (!response.ok) {
        let errorData: ErrorResponse | string;
        try {
          errorData = await response.json() as ErrorResponse;
        } catch {
          errorData = await response.text();
        }
        
        const message = typeof errorData === 'object' 
          ? errorData.detail 
          : `HTTP ${response.status}: ${response.statusText}`;
          
        throw new HttpClientError(message, response.status, errorData);
      }
      
      const contentType = response.headers.get('content-type');
      let data: T;
      
      if (contentType?.includes('application/json')) {
        data = await response.json() as T;
      } else if (contentType?.includes('text/xml') || contentType?.includes('application/xml')) {
        data = await response.text() as T;
      } else if (contentType?.includes('application/protobuf') || contentType?.includes('application/octet-stream')) {
        data = await response.arrayBuffer() as T;
      } else {
        data = await response.text() as T;
      }
      
      return {
        data,
        status: response.status,
        statusText: response.statusText,
        headers: response.headers,
      };
    } catch (error) {
      clearTimeout(timeoutId);
      
      if (error instanceof HttpClientError) {
        throw error;
      }
      
      if (error instanceof Error) {
        if (error.name === 'AbortError') {
          throw new HttpClientError('Request timeout', 408);
        }
        throw new HttpClientError(error.message, 0);
      }
      
      throw new HttpClientError('Unknown error occurred', 0);
    }
  }

  /**
   * Perform GET request
   */
  async get<T>(endpoint: string, params?: Record<string, any>): Promise<HttpResponse<T>> {
    return this.request<T>(endpoint, { method: 'GET', params });
  }

  /**
   * Perform POST request
   */
  async post<T>(endpoint: string, body?: any, params?: Record<string, any>): Promise<HttpResponse<T>> {
    return this.request<T>(endpoint, {
      method: 'POST',
      params,
      body: JSON.stringify(body),
      headers: {
        'Content-Type': 'application/json',
      },
    });
  }

  /**
   * Perform PUT request
   */
  async put<T>(endpoint: string, body?: any, params?: Record<string, any>): Promise<HttpResponse<T>> {
    return this.request<T>(endpoint, {
      method: 'PUT',
      params,
      body: JSON.stringify(body),
      headers: {
        'Content-Type': 'application/json',
      },
    });
  }

  /**
   * Perform DELETE request
   */
  async delete<T>(endpoint: string, params?: Record<string, any>): Promise<HttpResponse<T>> {
    return this.request<T>(endpoint, { method: 'DELETE', params });
  }
}
