/**
 * @ai-summary Google Maps client for station discovery
 * @ai-context Searches for gas stations and caches results
 */

import axios from 'axios';
import { appConfig } from '../../../../core/config/config-loader';
import { logger } from '../../../../core/logging/logger';
import { cacheService } from '../../../../core/config/redis';
import { GooglePlacesResponse, GoogleTextSearchResponse, GooglePlace } from './google-maps.types';
import { Station } from '../../domain/stations.types';

export class GoogleMapsClient {
  private readonly apiKey = appConfig.secrets.google_maps_api_key;
  private readonly baseURL = 'https://maps.googleapis.com/maps/api/place';
  private readonly cacheTTL = 3600; // 1 hour

  async searchNearbyStations(
    latitude: number,
    longitude: number,
    radius: number = 5000
  ): Promise<Station[]> {
    const cacheKey = `stations:${latitude.toFixed(4)},${longitude.toFixed(4)},${radius}`;
    
    try {
      // Check cache
      const cached = await cacheService.get<Station[]>(cacheKey);
      if (cached) {
        logger.debug('Station search cache hit', { latitude, longitude });
        return cached;
      }

      // Search Google Places
      logger.info('Searching Google Places for stations', { latitude, longitude, radius });
      
      const response = await axios.get<GooglePlacesResponse>(
        `${this.baseURL}/nearbysearch/json`,
        {
          params: {
            location: `${latitude},${longitude}`,
            radius,
            type: 'gas_station',
            key: this.apiKey
          }
        }
      );

      if (response.data.status !== 'OK' && response.data.status !== 'ZERO_RESULTS') {
        throw new Error(`Google Places API error: ${response.data.status}`);
      }

      // Transform results
      const stations = response.data.results.map(place => 
        this.transformPlaceToStation(place, latitude, longitude)
      );

      // Cache results
      await cacheService.set(cacheKey, stations, this.cacheTTL);

      return stations;
    } catch (error) {
      logger.error('Station search failed', { error, latitude, longitude });
      throw error;
    }
  }

  private transformPlaceToStation(place: GooglePlace, searchLat: number, searchLng: number): Station {
    const distance = this.calculateDistance(
      searchLat,
      searchLng,
      place.geometry.location.lat,
      place.geometry.location.lng
    );

    // Store photo reference instead of full URL to avoid exposing API key
    let photoReference: string | undefined;
    if (place.photos && place.photos.length > 0 && place.photos[0]) {
      photoReference = place.photos[0].photo_reference;
    }

    const station: Station = {
      id: place.place_id,
      placeId: place.place_id,
      name: place.name,
      address: place.vicinity,
      latitude: place.geometry.location.lat,
      longitude: place.geometry.location.lng,
      distance
    };

    if (photoReference !== undefined) {
      station.photoReference = photoReference;
    }

    if (place.opening_hours?.open_now !== undefined) {
      station.isOpen = place.opening_hours.open_now;
    }

    if (place.rating !== undefined) {
      station.rating = place.rating;
    }

    return station;
  }

  /**
   * Search for a gas station by merchant name using Google Places Text Search API.
   * Used to match receipt merchant names (e.g. "Shell", "COSTCO #123") to actual stations.
   */
  async searchStationByName(merchantName: string): Promise<Station | null> {
    const query = `${merchantName} gas station`;
    const cacheKey = `station-match:${query.toLowerCase().trim()}`;

    try {
      const cached = await cacheService.get<Station | null>(cacheKey);
      if (cached !== undefined && cached !== null) {
        logger.debug('Station name match cache hit', { merchantName });
        return cached;
      }

      logger.info('Searching Google Places Text Search for station', { merchantName, query });

      const response = await axios.get<GoogleTextSearchResponse>(
        `${this.baseURL}/textsearch/json`,
        {
          params: {
            query,
            type: 'gas_station',
            key: this.apiKey,
          },
          timeout: 5000,
        }
      );

      if (response.data.status !== 'OK' && response.data.status !== 'ZERO_RESULTS') {
        throw new Error(`Google Places Text Search API error: ${response.data.status}`);
      }

      if (response.data.results.length === 0) {
        await cacheService.set(cacheKey, null, this.cacheTTL);
        return null;
      }

      const topResult = response.data.results[0];
      const station = this.transformTextSearchResult(topResult);

      await cacheService.set(cacheKey, station, this.cacheTTL);
      return station;
    } catch (error: any) {
      if (error.code === 'ECONNABORTED' || error.message?.includes('timeout')) {
        logger.warn('Station name search timed out', { merchantName, timeoutMs: 5000 });
      } else {
        logger.error('Station name search failed', { error, merchantName });
      }
      return null;
    }
  }

  private transformTextSearchResult(place: GooglePlace): Station {
    let photoReference: string | undefined;
    if (place.photos && place.photos.length > 0 && place.photos[0]) {
      photoReference = place.photos[0].photo_reference;
    }

    // Text Search returns formatted_address instead of vicinity
    const address = (place as any).formatted_address || place.vicinity || '';

    const station: Station = {
      id: place.place_id,
      placeId: place.place_id,
      name: place.name,
      address,
      latitude: place.geometry.location.lat,
      longitude: place.geometry.location.lng,
    };

    if (photoReference !== undefined) {
      station.photoReference = photoReference;
    }

    if (place.opening_hours?.open_now !== undefined) {
      station.isOpen = place.opening_hours.open_now;
    }

    if (place.rating !== undefined) {
      station.rating = place.rating;
    }

    return station;
  }

  /**
   * Fetch photo from Google Maps API using photo reference
   * Used by photo proxy endpoint to serve photos without exposing API key
   */
  async fetchPhoto(photoReference: string, maxWidth: number = 400): Promise<Buffer> {
    try {
      const response = await axios.get(
        `${this.baseURL}/photo`,
        {
          params: {
            photo_reference: photoReference,
            maxwidth: maxWidth,
            key: this.apiKey
          },
          responseType: 'arraybuffer'
        }
      );

      return Buffer.from(response.data);
    } catch (error) {
      logger.error('Failed to fetch photo from Google Maps', { error, photoReference });
      throw error;
    }
  }

  private calculateDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
    const R = 6371e3; // Earth's radius in meters
    const φ1 = lat1 * Math.PI / 180;
    const φ2 = lat2 * Math.PI / 180;
    const Δφ = (lat2 - lat1) * Math.PI / 180;
    const Δλ = (lon2 - lon1) * Math.PI / 180;

    const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
              Math.cos(φ1) * Math.cos(φ2) *
              Math.sin(Δλ/2) * Math.sin(Δλ/2);
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));

    return Math.round(R * c); // Distance in meters
  }
}

export const googleMapsClient = new GoogleMapsClient();