import type { Fares, FaresResponse, FaresSearchParams, BoundingBox } from '../types/index.js';
import { HttpClient } from './http-client.js';

/**
 * Client for interacting with the BODS Fares API
 * 
 * The Fares API provides access to bus fare information.
 * Data is updated every 24 hours around 06:00 GMT.
 */
export class FaresClient {
  constructor(private httpClient: HttpClient) {}

  /**
   * Search for fares datasets
   * 
   * @param params - Search parameters to filter fares
   * @returns Promise resolving to paginated fares results
   * 
   * @example
   * ```typescript
   * // Find all fares for a specific operator
   * const fares = await client.fares.search({
   *   noc: ['SCMN'],
   *   status: 'published'
   * });
   * 
   * // Search by geographic area (Liverpool area example)
   * const areaFares = await client.fares.search({
   *   boundingBox: [-2.930, 53.374, -3.085, 53.453]
   * });
   * ```
   */
  async search(params: FaresSearchParams = {}): Promise<FaresResponse> {
    const response = await this.httpClient.get<FaresResponse>('/api/v1/fares/dataset', params);
    return response.data;
  }

  /**
   * Get a specific fares dataset by ID
   * 
   * @param datasetId - The unique dataset identifier
   * @returns Promise resolving to the fares dataset
   * 
   * @example
   * ```typescript
   * const fares = await client.fares.getById(86);
   * console.log(fares.operatorName, fares.numOfFareZones);
   * ```
   */
  async getById(datasetId: number): Promise<Fares> {
    const response = await this.httpClient.get<Fares>(`/api/v1/fares/dataset/${datasetId}`);
    return response.data;
  }

  /**
   * Get all fares for a specific operator
   * 
   * @param noc - National Operator Code(s)
   * @param additionalParams - Additional search parameters
   * @returns Promise resolving to paginated fares results
   * 
   * @example
   * ```typescript
   * const operatorFares = await client.fares.getByOperator(['SCMN', 'SCGH']);
   * ```
   */
  async getByOperator(
    noc: string | string[], 
    additionalParams: Omit<FaresSearchParams, 'noc'> = {}
  ): Promise<FaresResponse> {
    return this.search({
      ...additionalParams,
      noc: Array.isArray(noc) ? noc : [noc]
    });
  }

  /**
   * Get fares for a specific geographic area
   * 
   * @param boundingBox - Geographic bounding box [minLng, minLat, maxLng, maxLat]
   * @param additionalParams - Additional search parameters
   * @returns Promise resolving to paginated fares results
   * 
   * @example
   * ```typescript
   * // Liverpool area
   * const liverpoolFares = await client.fares.getByArea(
   *   [-2.930, 53.374, -3.085, 53.453]
   * );
   * 
   * // Manchester area
   * const manchesterFares = await client.fares.getByArea(
   *   [-2.3, 53.4, -2.2, 53.5]
   * );
   * ```
   */
  async getByArea(
    boundingBox: BoundingBox,
    additionalParams: Omit<FaresSearchParams, 'boundingBox'> = {}
  ): Promise<FaresResponse> {
    return this.search({
      ...additionalParams,
      boundingBox
    });
  }

  /**
   * Get published fares datasets only
   * 
   * @param additionalParams - Additional search parameters
   * @returns Promise resolving to paginated fares results
   * 
   * @example
   * ```typescript
   * const publishedFares = await client.fares.getPublished({
   *   limit: 100
   * });
   * ```
   */
  async getPublished(
    additionalParams: Omit<FaresSearchParams, 'status'> = {}
  ): Promise<FaresResponse> {
    return this.search({
      ...additionalParams,
      status: 'published'
    });
  }

  /**
   * Helper method to create bounding box from center point and radius
   * 
   * @param centerLat - Center latitude
   * @param centerLng - Center longitude  
   * @param radiusKm - Radius in kilometers
   * @returns Bounding box coordinates
   * 
   * @example
   * ```typescript
   * const bbox = client.fares.createBoundingBox(53.4808, -2.2426, 10); // Manchester, 10km radius
   * const nearbyFares = await client.fares.getByArea(bbox);
   * ```
   */
  createBoundingBox(centerLat: number, centerLng: number, radiusKm: number): BoundingBox {
    // Approximate degrees per kilometer
    const latDegPerKm = 1 / 111;
    const lngDegPerKm = 1 / (111 * Math.cos(centerLat * Math.PI / 180));
    
    const latOffset = radiusKm * latDegPerKm;
    const lngOffset = radiusKm * lngDegPerKm;
    
    return [
      centerLng - lngOffset, // minLng
      centerLat - latOffset, // minLat
      centerLng + lngOffset, // maxLng
      centerLat + latOffset  // maxLat
    ];
  }
}
