import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { map, catchError, timeout, retry } from 'rxjs/operators';
import { GEOCODING_CONFIG } from './geocoding.config';
import { environment } from '../../../../environments/environment';

// 🌍 Google Geocoding API Interfaces
export interface GoogleGeocodingResponse {
  results: GoogleGeocodingResult[];
  status: string;
  error_message?: string;
}

export interface GoogleGeocodingResult {
  address_components: GoogleAddressComponent[];
  formatted_address: string;
  geometry: {
    location: {
      lat: number;
      lng: number;
    };
    location_type: string;
    viewport: {
      northeast: { lat: number; lng: number };
      southwest: { lat: number; lng: number };
    };
  };
  place_id: string;
  types: string[];
}

export interface GoogleAddressComponent {
  long_name: string;
  short_name: string;
  types: string[];
}

// 📍 Interfaces do Sistema
export interface GeocodingResult {
  latitude: number;
  longitude: number;
  address: string;
  city?: string;
  state?: string;
  country?: string;
  postalCode?: string;
  formattedAddress?: string;
  placeId?: string;
}

export interface LocationCoordinates {
  latitude: number;
  longitude: number;
}

@Injectable({
  providedIn: 'root'
})
export class GeocodingService {
  constructor(private http: HttpClient) {}

  /**
   * 🔍 Converte coordenadas (lat, lng) em endereço usando Google Maps API
   * @param latitude Latitude
   * @param longitude Longitude
   * @param language Idioma (pt-BR, en, es)
   * @returns Observable com resultado da geocodificação reversa
   */
  reverseGeocode(latitude: number, longitude: number, language: string = 'pt-BR'): Observable<GeocodingResult> {
    const params = new HttpParams()
      .set('latlng', `${latitude},${longitude}`)
      .set('key', environment.googleMapsApiKey)
      .set('language', language)
      .set('region', GEOCODING_CONFIG.region);
      // Só iniciar se existir lat e lng 
      // // console.log('🔍 [GEOCODING] Parâmetros da requisição:', {
    return this.http.get<GoogleGeocodingResponse>(GEOCODING_CONFIG.baseUrl, { params })
      .pipe(
        timeout(GEOCODING_CONFIG.timeout),
        retry(GEOCODING_CONFIG.retryAttempts),
        map(response => {
          if (response.status !== 'OK' || !response.results?.length) {
            throw new Error(response.error_message || 'Nenhum resultado encontrado');
          }
          return this.parseGoogleResponse(response.results[0]);
        }),
        catchError(error => {
          console.error('❌ Erro na geocodificação reversa (Google):', error);
          return throwError(() => new Error('Falha ao obter endereço das coordenadas'));
        })
      );
  }

  /**
   * 🗺️ Converte endereço em coordenadas usando Google Maps API
   * @param address Endereço para buscar
   * @param language Idioma (pt-BR, en, es)
   * @returns Observable com resultado da geocodificação
   */
  geocode(address: string, language: string = 'pt-BR'): Observable<GeocodingResult[]> {
    const params = new HttpParams()
      .set('address', address)
      .set('key', environment.googleMapsApiKey)
      .set('language', language)
      .set('region', GEOCODING_CONFIG.region)
      .set('components', 'country:BR'); // Priorizar resultados do Brasil

    return this.http.get<GoogleGeocodingResponse>(GEOCODING_CONFIG.baseUrl, { params })
      .pipe(
        timeout(GEOCODING_CONFIG.timeout),
        retry(GEOCODING_CONFIG.retryAttempts),
        map(response => {
          if (response.status !== 'OK') {
            if (response.status === 'ZERO_RESULTS') {
              return [];
            }
            throw new Error(response.error_message || `Erro da API: ${response.status}`);
          }
          return response.results.map(result => this.parseGoogleResponse(result));
        }),
        catchError(error => {
          console.error('❌ Erro na geocodificação (Google):', error);
          return throwError(() => new Error('Falha ao obter coordenadas do endereço'));
        })
      );
  }

  /**
   * 📍 Obtém localização atual do usuário
   * @returns Promise com coordenadas atuais
   */
  getCurrentLocation(): Promise<LocationCoordinates> {
    return new Promise((resolve, reject) => {
      if (!navigator.geolocation) {
        reject(new Error('Geolocalização não é suportada neste navegador'));
        return;
      }

      const options: PositionOptions = {
        enableHighAccuracy: true,
        timeout: 15000,
        maximumAge: 300000 // 5 minutos
      };

      navigator.geolocation.getCurrentPosition(
        (position) => {
          resolve({
            latitude: position.coords.latitude,
            longitude: position.coords.longitude
          });
        },
        (error) => {
          let errorMessage = 'Erro ao obter localização atual';
          
          switch (error.code) {
            case error.PERMISSION_DENIED:
              errorMessage = 'Permissão de localização negada pelo usuário';
              break;
            case error.POSITION_UNAVAILABLE:
              errorMessage = 'Informações de localização não disponíveis';
              break;
            case error.TIMEOUT:
              errorMessage = 'Tempo limite para obter localização excedido';
              break;
          }
          
          console.error('❌ Erro de geolocalização:', errorMessage);
          reject(new Error(errorMessage));
        },
        options
      );
    });
  }

  /**
   * 📏 Calcula distância entre duas coordenadas usando fórmula de Haversine
   * @param lat1 Latitude do ponto 1
   * @param lon1 Longitude do ponto 1
   * @param lat2 Latitude do ponto 2
   * @param lon2 Longitude do ponto 2
   * @returns Distância em quilômetros
   */
  calculateDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
    const R = 6371; // Raio da Terra em km
    const dLat = this.toRadians(lat2 - lat1);
    const dLon = this.toRadians(lon2 - lon1);
    
    const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
              Math.cos(this.toRadians(lat1)) * Math.cos(this.toRadians(lat2)) *
              Math.sin(dLon / 2) * Math.sin(dLon / 2);
    
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    return R * c;
  }

  /**
   * 📐 Formata distância para exibição
   * @param distanceKm Distância em quilômetros
   * @returns String formatada com unidade apropriada
   */
  formatDistance(distanceKm: number): string {
    if (distanceKm < 1) {
      return `${Math.round(distanceKm * 1000)}m`;
    } else if (distanceKm < 10) {
      return `${distanceKm.toFixed(1)}km`;
    } else {
      return `${Math.round(distanceKm)}km`;
    }
  }

  /**
   * 🔄 Converte graus para radianos
   */
  private toRadians(degrees: number): number {
    return degrees * (Math.PI / 180);
  }

  /**
   * 🔍 Processa resposta da Google Geocoding API
   */
  private parseGoogleResponse(result: GoogleGeocodingResult): GeocodingResult {
    const components = result.address_components;
    
    // 🔍 Extrai componentes específicos do endereço
    const getComponent = (types: string[]) => {
      const component = components.find(c => types.some(type => c.types.includes(type)));
      return component?.long_name || '';
    };

    const getShortComponent = (types: string[]) => {
      const component = components.find(c => types.some(type => c.types.includes(type)));
      return component?.short_name || '';
    };

    return {
      latitude: result.geometry.location.lat,
      longitude: result.geometry.location.lng,
      address: result.formatted_address,
      city: getComponent(['locality', 'administrative_area_level_2']),
      state: getShortComponent(['administrative_area_level_1']),
      country: getShortComponent(['country']),
      postalCode: getComponent(['postal_code']),
      formattedAddress: result.formatted_address,
      placeId: result.place_id
    };
  }
} 