"""
Unified Map Service - Main orchestrating service for all map functionality.
"""

import time
from typing import Any

from django.db import connection

from .clustering_service import ClusteringService
from .data_structures import (
    ClusterData,
    GeoBounds,
    LocationType,
    MapFilters,
    MapResponse,
    QueryPerformanceMetrics,
    UnifiedLocation,
)
from .location_adapters import LocationAbstractionLayer
from .map_cache_service import MapCacheService


class UnifiedMapService:
    """
    Main service orchestrating map data retrieval, filtering, clustering, and caching.
    Provides a unified interface for all location types with performance optimization.
    """

    # Performance thresholds
    MAX_UNCLUSTERED_POINTS = 500
    MAX_CLUSTERED_POINTS = 2000
    DEFAULT_ZOOM_LEVEL = 10

    def __init__(self):
        self.location_layer = LocationAbstractionLayer()
        self.clustering_service = ClusteringService()
        self.cache_service = MapCacheService()

    def get_map_data(
        self,
        *,
        bounds: GeoBounds | None = None,
        filters: MapFilters | None = None,
        zoom_level: int = DEFAULT_ZOOM_LEVEL,
        cluster: bool = True,
        use_cache: bool = True,
    ) -> MapResponse:
        """
        Primary method for retrieving unified map data.

        Args:
            bounds: Geographic bounds to query within
            filters: Filtering criteria for locations
            zoom_level: Map zoom level for clustering decisions
            cluster: Whether to apply clustering
            use_cache: Whether to use cached data

        Returns:
            MapResponse with locations, clusters, and metadata
        """
        start_time = time.time()
        initial_query_count = len(connection.queries)
        cache_hit = False

        try:
            # Generate cache key
            cache_key = None
            if use_cache:
                cache_key = self._generate_cache_key(bounds, filters, zoom_level, cluster)

                # Try to get from cache first
                cached_response = self.cache_service.get_cached_map_response(cache_key)
                if cached_response:
                    cached_response.cache_hit = True
                    cached_response.query_time_ms = int((time.time() - start_time) * 1000)
                    return cached_response

            # Get locations from database
            locations = self._get_locations_from_db(bounds, filters)

            # Apply smart limiting based on zoom level and density
            locations = self._apply_smart_limiting(locations, bounds, zoom_level)

            # Determine if clustering should be applied
            should_cluster = cluster and self.clustering_service.should_cluster(zoom_level, len(locations))

            # Apply clustering if needed
            clusters = []
            if should_cluster:
                locations, clusters = self.clustering_service.cluster_locations(locations, zoom_level, bounds)

            # Calculate response bounds
            response_bounds = self._calculate_response_bounds(locations, clusters, bounds)

            # Create response
            response = MapResponse(
                locations=locations,
                clusters=clusters,
                bounds=response_bounds,
                total_count=len(locations) + sum(cluster.count for cluster in clusters),
                filtered_count=len(locations),
                zoom_level=zoom_level,
                clustered=should_cluster,
                cache_hit=cache_hit,
                query_time_ms=int((time.time() - start_time) * 1000),
                filters_applied=self._get_applied_filters_list(filters),
            )

            # Cache the response
            if use_cache and cache_key:
                self.cache_service.cache_map_response(cache_key, response)

            # Record performance metrics
            self._record_performance_metrics(
                start_time,
                initial_query_count,
                cache_hit,
                len(locations) + len(clusters),
                bounds is not None,
                should_cluster,
            )

            return response

        except Exception:
            # Return error response
            return MapResponse(
                locations=[],
                clusters=[],
                total_count=0,
                filtered_count=0,
                query_time_ms=int((time.time() - start_time) * 1000),
                cache_hit=False,
            )

    def get_location_details(self, location_type: str, location_id: int) -> UnifiedLocation | None:
        """
        Get detailed information for a specific location.

        Args:
            location_type: Type of location (park, ride, company, generic)
            location_id: ID of the location

        Returns:
            UnifiedLocation with full details or None if not found
        """
        try:
            # Check cache first
            cache_key = self.cache_service.get_location_detail_cache_key(location_type, location_id)
            cached_locations = self.cache_service.get_cached_locations(cache_key)
            if cached_locations:
                return cached_locations[0] if cached_locations else None

            # Get from database
            location_type_enum = LocationType(location_type.lower())
            location = self.location_layer.get_location_by_id(location_type_enum, location_id)

            # Cache the result
            if location:
                self.cache_service.cache_locations(
                    cache_key,
                    [location],
                    self.cache_service.LOCATION_DETAIL_TTL,
                )

            return location

        except Exception as e:
            print(f"Error getting location details: {e}")
            return None

    def search_locations(
        self,
        query: str,
        bounds: GeoBounds | None = None,
        location_types: set[LocationType] | None = None,
        limit: int = 50,
    ) -> list[UnifiedLocation]:
        """
        Search locations with text query.

        Args:
            query: Search query string
            bounds: Optional geographic bounds to search within
            location_types: Optional set of location types to search
            limit: Maximum number of results

        Returns:
            List of matching UnifiedLocation objects
        """
        try:
            # Create search filters
            filters = MapFilters(
                search_query=query,
                location_types=location_types or {LocationType.PARK, LocationType.RIDE},
                has_coordinates=True,
            )

            # Get locations
            locations = self.location_layer.get_all_locations(bounds, filters)

            # Apply limit
            return locations[:limit]

        except Exception as e:
            print(f"Error searching locations: {e}")
            return []

    def get_locations_by_bounds(
        self,
        north: float,
        south: float,
        east: float,
        west: float,
        location_types: set[LocationType] | None = None,
        zoom_level: int = DEFAULT_ZOOM_LEVEL,
    ) -> MapResponse:
        """
        Get locations within specific geographic bounds.

        Args:
            north, south, east, west: Bounding box coordinates
            location_types: Optional filter for location types
            zoom_level: Map zoom level for optimization

        Returns:
            MapResponse with locations in bounds
        """
        try:
            bounds = GeoBounds(north=north, south=south, east=east, west=west)
            filters = MapFilters(location_types=location_types) if location_types else None

            return self.get_map_data(bounds=bounds, filters=filters, zoom_level=zoom_level)

        except ValueError:
            # Invalid bounds
            return MapResponse(locations=[], clusters=[], total_count=0, filtered_count=0)

    def get_clustered_locations(
        self,
        zoom_level: int,
        bounds: GeoBounds | None = None,
        filters: MapFilters | None = None,
    ) -> MapResponse:
        """
        Get clustered location data for map display.

        Args:
            zoom_level: Map zoom level for clustering configuration
            bounds: Optional geographic bounds
            filters: Optional filtering criteria

        Returns:
            MapResponse with clustered data
        """
        return self.get_map_data(bounds=bounds, filters=filters, zoom_level=zoom_level, cluster=True)

    def get_locations_by_type(
        self,
        location_type: LocationType,
        bounds: GeoBounds | None = None,
        limit: int | None = None,
    ) -> list[UnifiedLocation]:
        """
        Get locations of a specific type.

        Args:
            location_type: Type of locations to retrieve
            bounds: Optional geographic bounds
            limit: Optional limit on results

        Returns:
            List of UnifiedLocation objects
        """
        try:
            filters = MapFilters(location_types={location_type})
            locations = self.location_layer.get_locations_by_type(location_type, bounds, filters)

            if limit:
                locations = locations[:limit]

            return locations

        except Exception as e:
            print(f"Error getting locations by type: {e}")
            return []

    def invalidate_cache(
        self,
        location_type: str | None = None,
        location_id: int | None = None,
        bounds: GeoBounds | None = None,
    ) -> None:
        """
        Invalidate cached map data.

        Args:
            location_type: Optional specific location type to invalidate
            location_id: Optional specific location ID to invalidate
            bounds: Optional specific bounds to invalidate
        """
        if location_type and location_id:
            self.cache_service.invalidate_location_cache(location_type, location_id)
        elif bounds:
            self.cache_service.invalidate_bounds_cache(bounds)
        else:
            self.cache_service.clear_all_map_cache()

    def get_service_stats(self) -> dict[str, Any]:
        """Get service performance and usage statistics."""
        cache_stats = self.cache_service.get_cache_stats()

        return {
            "cache_performance": cache_stats,
            "clustering_available": True,
            "supported_location_types": [t.value for t in LocationType],
            "max_unclustered_points": self.MAX_UNCLUSTERED_POINTS,
            "max_clustered_points": self.MAX_CLUSTERED_POINTS,
            "service_version": "1.0.0",
        }

    def _get_locations_from_db(self, bounds: GeoBounds | None, filters: MapFilters | None) -> list[UnifiedLocation]:
        """Get locations from database using the abstraction layer."""
        return self.location_layer.get_all_locations(bounds, filters)

    def _apply_smart_limiting(
        self,
        locations: list[UnifiedLocation],
        bounds: GeoBounds | None,
        zoom_level: int,
    ) -> list[UnifiedLocation]:
        """Apply intelligent limiting based on zoom level and density."""
        if zoom_level < 6:  # Very zoomed out - show only major parks
            major_parks = [
                loc
                for loc in locations
                if (loc.type == LocationType.PARK and loc.cluster_category in ["major_park", "theme_park"])
            ]
            return major_parks[:200]
        elif zoom_level < 10:  # Regional level
            return locations[:1000]
        else:  # City level and closer
            return locations[: self.MAX_CLUSTERED_POINTS]

    def _calculate_response_bounds(
        self,
        locations: list[UnifiedLocation],
        clusters: list[ClusterData],
        request_bounds: GeoBounds | None,
    ) -> GeoBounds | None:
        """Calculate the actual bounds of the response data."""
        if request_bounds:
            return request_bounds

        all_coords = []

        # Add location coordinates
        for loc in locations:
            all_coords.append((loc.latitude, loc.longitude))

        # Add cluster coordinates
        for cluster in clusters:
            all_coords.append(cluster.coordinates)

        if not all_coords:
            return None

        lats, lngs = zip(*all_coords, strict=False)
        return GeoBounds(north=max(lats), south=min(lats), east=max(lngs), west=min(lngs))

    def _get_applied_filters_list(self, filters: MapFilters | None) -> list[str]:
        """Get list of applied filter types for metadata."""
        if not filters:
            return []

        applied = []
        if filters.location_types:
            applied.append("location_types")
        if filters.search_query:
            applied.append("search_query")
        if filters.park_status:
            applied.append("park_status")
        if filters.ride_types:
            applied.append("ride_types")
        if filters.company_roles:
            applied.append("company_roles")
        if filters.min_rating:
            applied.append("min_rating")
        if filters.country:
            applied.append("country")
        if filters.state:
            applied.append("state")
        if filters.city:
            applied.append("city")

        return applied

    def _generate_cache_key(
        self,
        bounds: GeoBounds | None,
        filters: MapFilters | None,
        zoom_level: int,
        cluster: bool,
    ) -> str:
        """Generate cache key for the request."""
        if cluster:
            return self.cache_service.get_clusters_cache_key(bounds, filters, zoom_level)
        else:
            return self.cache_service.get_locations_cache_key(bounds, filters, zoom_level)

    def _record_performance_metrics(
        self,
        start_time: float,
        initial_query_count: int,
        cache_hit: bool,
        result_count: int,
        bounds_used: bool,
        clustering_used: bool,
    ) -> None:
        """Record performance metrics for monitoring."""
        query_time_ms = int((time.time() - start_time) * 1000)
        db_query_count = len(connection.queries) - initial_query_count

        metrics = QueryPerformanceMetrics(
            query_time_ms=query_time_ms,
            db_query_count=db_query_count,
            cache_hit=cache_hit,
            result_count=result_count,
            bounds_used=bounds_used,
            clustering_used=clustering_used,
        )

        self.cache_service.record_performance_metrics(metrics)


# Global service instance
unified_map_service = UnifiedMapService()
