export interface Location {
  lat: number;
  lng: number;
  address?: string;
}

export interface DistanceResult {
  distance: number; // in kilometers
  duration: number; // in minutes (estimated)
}

export class GeolocationService {
  private static instance: GeolocationService;

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

  /**
   * Get current user position
   */
  async getCurrentPosition(): Promise<Location> {
    return new Promise((resolve, reject) => {
      if (!navigator.geolocation) {
        reject(new Error('Géolocalisation non supportée par ce navigateur'));
        return;
      }

      navigator.geolocation.getCurrentPosition(
        (position) => {
          resolve({
            lat: position.coords.latitude,
            lng: position.coords.longitude
          });
        },
        (error) => {
          let message = 'Erreur de géolocalisation';
          switch (error.code) {
            case error.PERMISSION_DENIED:
              message = 'Permission de géolocalisation refusée';
              break;
            case error.POSITION_UNAVAILABLE:
              message = 'Position indisponible';
              break;
            case error.TIMEOUT:
              message = 'Timeout de géolocalisation';
              break;
          }
          reject(new Error(message));
        },
        {
          enableHighAccuracy: true,
          timeout: 10000,
          maximumAge: 300000 // 5 minutes
        }
      );
    });
  }

  /**
   * Calculate distance between two points using Haversine formula
   */
  calculateDistance(point1: Location, point2: Location): DistanceResult {
    const R = 6371; // Earth's radius in kilometers
    const dLat = this.toRad(point2.lat - point1.lat);
    const dLng = this.toRad(point2.lng - point1.lng);
    const a = 
      Math.sin(dLat/2) * Math.sin(dLat/2) +
      Math.cos(this.toRad(point1.lat)) * Math.cos(this.toRad(point2.lat)) * 
      Math.sin(dLng/2) * Math.sin(dLng/2);
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
    const distance = R * c;

    // Estimate duration (assuming average speed in urban area)
    // Speed estimates: walking 5km/h, bike 15km/h, car 30km/h in city
    const estimatedSpeed = 20; // km/h average for delivery
    const duration = (distance / estimatedSpeed) * 60; // in minutes

    return {
      distance: Math.round(distance * 100) / 100, // Round to 2 decimal places
      duration: Math.ceil(duration)
    };
  }

  /**
   * Find nearby points within a given radius
   */
  findNearbyPoints(center: Location, points: (Location & { id: string })[], radiusKm: number = 10): Array<Location & { id: string; distance: number; duration: number }> {
    return points
      .map(point => {
        const result = this.calculateDistance(center, point);
        return {
          ...point,
          distance: result.distance,
          duration: result.duration
        };
      })
      .filter(point => point.distance <= radiusKm)
      .sort((a, b) => a.distance - b.distance);
  }

  /**
   * Reverse geocoding - convert coordinates to address (basic implementation)
   */
  async reverseGeocode(location: Location): Promise<string> {
    try {
      const response = await fetch(
        `https://nominatim.openstreetmap.org/reverse?format=json&lat=${location.lat}&lon=${location.lng}&zoom=18&addressdetails=1`,
        {
          headers: {
            'User-Agent': 'MediFlash-SOS+'
          }
        }
      );
      
      if (!response.ok) {
        throw new Error('Geocoding API error');
      }
      
      const data = await response.json();
      
      if (data.display_name) {
        return data.display_name;
      }
      
      throw new Error('Address not found');
    } catch (error) {
      console.warn('Reverse geocoding failed:', error);
      return `${location.lat.toFixed(4)}, ${location.lng.toFixed(4)}`;
    }
  }

  /**
   * Forward geocoding - convert address to coordinates
   */
  async geocode(address: string): Promise<Location> {
    try {
      const response = await fetch(
        `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(address)}&limit=1&countrycodes=ci`,
        {
          headers: {
            'User-Agent': 'MediFlash-SOS+'
          }
        }
      );
      
      if (!response.ok) {
        throw new Error('Geocoding API error');
      }
      
      const data = await response.json();
      
      if (data.length > 0) {
        return {
          lat: parseFloat(data[0].lat),
          lng: parseFloat(data[0].lon),
          address: data[0].display_name
        };
      }
      
      throw new Error('Address not found');
    } catch (error) {
      console.warn('Geocoding failed:', error);
      throw new Error('Impossible de localiser cette adresse');
    }
  }

  /**
   * Get Abidjan bounds for map initialization
   */
  getAbidjanBounds(): { center: [number, number]; bounds: [[number, number], [number, number]] } {
    return {
      center: [5.3364, -4.0267], // Plateau, Abidjan
      bounds: [
        [5.2, -4.2], // Southwest
        [5.5, -3.8]  // Northeast
      ]
    };
  }

  /**
   * Check if location is within Abidjan area
   */
  isWithinServiceArea(location: Location): boolean {
    const bounds = this.getAbidjanBounds();
    return (
      location.lat >= bounds.bounds[0][0] &&
      location.lat <= bounds.bounds[1][0] &&
      location.lng >= bounds.bounds[0][1] &&
      location.lng <= bounds.bounds[1][1]
    );
  }

  /**
   * Format distance for display
   */
  formatDistance(distanceKm: number): string {
    if (distanceKm < 1) {
      return `${Math.round(distanceKm * 1000)}m`;
    }
    return `${distanceKm.toFixed(1)}km`;
  }

  /**
   * Format duration for display
   */
  formatDuration(minutes: number): string {
    if (minutes < 60) {
      return `${minutes}min`;
    }
    const hours = Math.floor(minutes / 60);
    const remainingMinutes = minutes % 60;
    return `${hours}h${remainingMinutes > 0 ? remainingMinutes + 'min' : ''}`;
  }

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