import { supabase } from './supabase';

export interface LocationData {
  latitude: number;
  longitude: number;
  city?: string;
  state?: string;
  country?: string;
  zipCode?: string;
  accuracy?: number;
  source: 'gps' | 'ip_geolocation' | 'manual';
}

export interface UserLocationPreference {
  userId?: string;
  sessionId: string;
  preferredLatitude: number;
  preferredLongitude: number;
  preferredCity?: string;
  preferredState?: string;
  preferredCountry?: string;
  preferredZipCode?: string;
  searchRadiusMiles: number;
  locationSource: 'gps' | 'manual' | 'ip_geolocation';
}

export class GeolocationService {
  private static instance: GeolocationService;
  private currentLocation: LocationData | null = null;
  private locationWatchers: ((location: LocationData | null) => void)[] = [];

  static getInstance(): GeolocationService {
    if (!GeolocationService.instance) {
      GeolocationService.instance = new GeolocationService();
    }
    return GeolocationService.instance;
  }

  async getCurrentLocation(): Promise<LocationData | null> {
    return new Promise((resolve) => {
      if (this.currentLocation) {
        resolve(this.currentLocation);
        return;
      }

      if (!navigator.geolocation) {

        resolve(null);
        return;
      }

      const options = {
        enableHighAccuracy: true,
        timeout: 10000,
        maximumAge: 300000 // 5 minutes
      };

      navigator.geolocation.getCurrentPosition(
        (position) => {
          const location: LocationData = {
            latitude: position.coords.latitude,
            longitude: position.coords.longitude,
            accuracy: position.coords.accuracy,
            source: 'gps'
          };

          this.currentLocation = location;
          this.notifyWatchers(location);
          resolve(location);
        },
        (error) => {

          resolve(null);
        },
        options
      );
    });
  }

  async getLocationFromIP(): Promise<LocationData | null> {
    try {
      const response = await fetch('https://ipapi.co/json/');
      const data = await response.json();
      
      if (data.latitude && data.longitude) {
        const location: LocationData = {
          latitude: data.latitude,
          longitude: data.longitude,
          city: data.city,
          state: data.region,
          country: data.country_code,
          zipCode: data.postal,
          source: 'ip_geolocation'
        };

        this.currentLocation = location;
        this.notifyWatchers(location);
        return location;
      }
    } catch (error) {

    }
    return null;
  }

  async geocodeAddress(address: string): Promise<LocationData | null> {
    try {
      const response = await fetch(`https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(address)}.json?access_token=${import.meta.env.PUBLIC_MAPBOX_TOKEN}&country=US&types=place,postcode,address`);
      const data = await response.json();
      
      if (data.features && data.features.length > 0) {
        const feature = data.features[0];
        const [longitude, latitude] = feature.center;
        
        const location: LocationData = {
          latitude,
          longitude,
          city: this.extractContextValue(feature.context, 'place'),
          state: this.extractContextValue(feature.context, 'region'),
          country: this.extractContextValue(feature.context, 'country'),
          zipCode: this.extractContextValue(feature.context, 'postcode'),
          source: 'manual'
        };

        return location;
      }
    } catch (error) {

    }
    return null;
  }

  private extractContextValue(context: any[], type: string): string | undefined {
    if (!context) return undefined;
    const item = context.find(c => c.id.startsWith(type));
    return item ? item.text : undefined;
  }

  async saveUserLocationPreference(preference: UserLocationPreference): Promise<void> {
    try {
      const { error } = await supabase
        .from('user_location_preferences')
        .upsert({
          user_id: preference.userId,
          session_id: preference.sessionId,
          preferred_latitude: preference.preferredLatitude,
          preferred_longitude: preference.preferredLongitude,
          preferred_city: preference.preferredCity,
          preferred_state: preference.preferredState,
          preferred_country: preference.preferredCountry,
          preferred_zip_code: preference.preferredZipCode,
          search_radius_miles: preference.searchRadiusMiles,
          location_source: preference.locationSource,
          updated_at: new Date().toISOString()
        });

      if (error) {

      }
    } catch (error) {

    }
  }

  async getUserLocationPreference(userId?: string, sessionId?: string): Promise<UserLocationPreference | null> {
    try {
      let query = supabase.from('user_location_preferences').select('*');
      
      if (userId) {
        query = query.eq('user_id', userId);
      } else if (sessionId) {
        query = query.eq('session_id', sessionId);
      } else {
        return null;
      }

      const { data, error } = await query.single();

      if (error || !data) {
        return null;
      }

      return {
        userId: data.user_id,
        sessionId: data.session_id,
        preferredLatitude: data.preferred_latitude,
        preferredLongitude: data.preferred_longitude,
        preferredCity: data.preferred_city,
        preferredState: data.preferred_state,
        preferredCountry: data.preferred_country,
        preferredZipCode: data.preferred_zip_code,
        searchRadiusMiles: data.search_radius_miles,
        locationSource: data.location_source
      };
    } catch (error) {

      return null;
    }
  }

  async requestLocationPermission(): Promise<LocationData | null> {
    try {
      const location = await this.getCurrentLocation();
      if (location) {
        return location;
      }
    } catch (error) {

    }

    return await this.getLocationFromIP();
  }

  watchLocation(callback: (location: LocationData | null) => void): () => void {
    this.locationWatchers.push(callback);
    
    // Immediately call with current location if available
    if (this.currentLocation) {
      callback(this.currentLocation);
    }

    // Return unsubscribe function
    return () => {
      this.locationWatchers = this.locationWatchers.filter(w => w !== callback);
    };
  }

  private notifyWatchers(location: LocationData | null): void {
    this.locationWatchers.forEach(callback => callback(location));
  }

  clearCurrentLocation(): void {
    this.currentLocation = null;
    this.notifyWatchers(null);
  }

  calculateDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
    const R = 3959; // Earth's radius in miles
    const dLat = this.toRad(lat2 - lat1);
    const dLon = this.toRad(lon2 - lon1);
    const a = 
      Math.sin(dLat/2) * Math.sin(dLat/2) +
      Math.cos(this.toRad(lat1)) * Math.cos(this.toRad(lat2)) * 
      Math.sin(dLon/2) * Math.sin(dLon/2);
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
    return R * c;
  }

  private toRad(value: number): number {
    return value * Math.PI / 180;
  }
}

export const geolocationService = GeolocationService.getInstance();