/**
 * Geolocation Service
 * Provides location-based features for connecting plant owners
 */

import { PlantData, PlantLocation } from '../blockchain/types';

export interface PlantCluster {
  centerLat: number;
  centerLon: number;
  plantCount: number;
  plants: PlantData[];
  radius: number; // in km
  dominantSpecies: string[];
}

export interface ConnectionSuggestion {
  plant1: PlantData;
  plant2: PlantData;
  distance: number;
  matchReason: string; // e.g., "same species", "same lineage", "nearby location"
  compatibilityScore: number; // 0-100
}

export class GeolocationService {
  /**
   * Calculate distance between two coordinates using Haversine formula
   */
  calculateDistance(
    lat1: number,
    lon1: number,
    lat2: number,
    lon2: number
  ): number {
    const R = 6371; // Earth's radius in 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;
  }

  private toRadians(degrees: number): number {
    return degrees * (Math.PI / 180);
  }

  /**
   * Find plant clusters in a region
   * Groups nearby plants together to show hotspots of activity
   */
  findPlantClusters(
    plants: PlantData[],
    clusterRadius: number = 10 // km
  ): PlantCluster[] {
    const clusters: PlantCluster[] = [];
    const processed = new Set<string>();

    for (const plant of plants) {
      if (processed.has(plant.id)) continue;

      // Find all plants within cluster radius
      const clusterPlants: PlantData[] = [plant];
      processed.add(plant.id);

      for (const otherPlant of plants) {
        if (processed.has(otherPlant.id)) continue;

        const distance = this.calculateDistance(
          plant.location.latitude,
          plant.location.longitude,
          otherPlant.location.latitude,
          otherPlant.location.longitude
        );

        if (distance <= clusterRadius) {
          clusterPlants.push(otherPlant);
          processed.add(otherPlant.id);
        }
      }

      // Calculate cluster center (average of all positions)
      const centerLat =
        clusterPlants.reduce((sum, p) => sum + p.location.latitude, 0) /
        clusterPlants.length;
      const centerLon =
        clusterPlants.reduce((sum, p) => sum + p.location.longitude, 0) /
        clusterPlants.length;

      // Find dominant species
      const speciesCount: { [key: string]: number } = {};
      for (const p of clusterPlants) {
        if (p.scientificName) {
          speciesCount[p.scientificName] =
            (speciesCount[p.scientificName] || 0) + 1;
        }
      }

      const dominantSpecies = Object.entries(speciesCount)
        .sort((a, b) => b[1] - a[1])
        .slice(0, 3)
        .map(([species]) => species);

      clusters.push({
        centerLat,
        centerLon,
        plantCount: clusterPlants.length,
        plants: clusterPlants,
        radius: clusterRadius,
        dominantSpecies,
      });
    }

    return clusters.sort((a, b) => b.plantCount - a.plantCount);
  }

  /**
   * Suggest connections between plant owners
   * Finds compatible plants for sharing/trading
   */
  suggestConnections(
    userPlant: PlantData,
    allPlants: PlantData[],
    maxDistance: number = 50 // km
  ): ConnectionSuggestion[] {
    const suggestions: ConnectionSuggestion[] = [];

    for (const otherPlant of allPlants) {
      // Skip if same owner
      if (otherPlant.owner.id === userPlant.owner.id) continue;

      // Skip if same plant
      if (otherPlant.id === userPlant.id) continue;

      const distance = this.calculateDistance(
        userPlant.location.latitude,
        userPlant.location.longitude,
        otherPlant.location.latitude,
        otherPlant.location.longitude
      );

      // Skip if too far
      if (distance > maxDistance) continue;

      let matchReason = '';
      let compatibilityScore = 0;

      // Check for same species
      if (
        userPlant.scientificName &&
        userPlant.scientificName === otherPlant.scientificName
      ) {
        matchReason = 'Same species';
        compatibilityScore += 40;
      }

      // Check for same lineage
      if (
        userPlant.parentPlantId === otherPlant.parentPlantId &&
        userPlant.parentPlantId
      ) {
        matchReason =
          matchReason + (matchReason ? ', ' : '') + 'Same parent plant';
        compatibilityScore += 30;
      }

      // Check for same genus
      if (userPlant.genus && userPlant.genus === otherPlant.genus) {
        if (!matchReason) matchReason = 'Same genus';
        compatibilityScore += 20;
      }

      // Proximity bonus
      const proximityScore = Math.max(0, 20 - distance / 2.5);
      compatibilityScore += proximityScore;

      // Only suggest if there's some compatibility
      if (compatibilityScore > 20) {
        if (!matchReason) matchReason = 'Nearby location';

        suggestions.push({
          plant1: userPlant,
          plant2: otherPlant,
          distance,
          matchReason,
          compatibilityScore: Math.min(100, compatibilityScore),
        });
      }
    }

    return suggestions.sort((a, b) => b.compatibilityScore - a.compatibilityScore);
  }

  /**
   * Get address from coordinates using reverse geocoding
   * Note: In production, you'd use a service like Google Maps or OpenStreetMap
   */
  async reverseGeocode(
    latitude: number,
    longitude: number
  ): Promise<Partial<PlantLocation>> {
    try {
      // Using OpenStreetMap Nominatim (free, but rate-limited)
      const response = await fetch(
        `https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude}&lon=${longitude}&zoom=18&addressdetails=1`,
        {
          headers: {
            'User-Agent': 'LocalGreenChain/1.0',
          },
        }
      );

      if (!response.ok) {
        console.error('Reverse geocoding error:', response.statusText);
        return { latitude, longitude };
      }

      const data = await response.json();

      return {
        latitude,
        longitude,
        address: data.display_name,
        city: data.address?.city || data.address?.town || data.address?.village,
        country: data.address?.country,
      };
    } catch (error) {
      console.error('Error in reverse geocoding:', error);
      return { latitude, longitude };
    }
  }

  /**
   * Get coordinates from address using forward geocoding
   */
  async geocode(
    address: string
  ): Promise<{ latitude: number; longitude: number } | null> {
    try {
      const response = await fetch(
        `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(address)}`,
        {
          headers: {
            'User-Agent': 'LocalGreenChain/1.0',
          },
        }
      );

      if (!response.ok) {
        console.error('Geocoding error:', response.statusText);
        return null;
      }

      const data = await response.json();
      if (data.length === 0) return null;

      return {
        latitude: parseFloat(data[0].lat),
        longitude: parseFloat(data[0].lon),
      };
    } catch (error) {
      console.error('Error in geocoding:', error);
      return null;
    }
  }

  /**
   * Check if a location is within a given boundary
   */
  isWithinBounds(
    location: { latitude: number; longitude: number },
    bounds: {
      north: number;
      south: number;
      east: number;
      west: number;
    }
  ): boolean {
    return (
      location.latitude <= bounds.north &&
      location.latitude >= bounds.south &&
      location.longitude <= bounds.east &&
      location.longitude >= bounds.west
    );
  }
}

// Singleton instance
let geolocationService: GeolocationService | null = null;

export function getGeolocationService(): GeolocationService {
  if (!geolocationService) {
    geolocationService = new GeolocationService();
  }
  return geolocationService;
}
