"""
Location adapters for converting between domain-specific models and UnifiedLocation.
"""

from django.db.models import QuerySet
from django.urls import reverse

from apps.parks.models import CompanyHeadquarters, ParkLocation
from apps.rides.models import RideLocation

from .data_structures import (
    GeoBounds,
    LocationType,
    MapFilters,
    UnifiedLocation,
)


class BaseLocationAdapter:
    """Base adapter class for location conversions."""

    def to_unified_location(self, location_obj) -> UnifiedLocation | None:
        """Convert model instance to UnifiedLocation."""
        raise NotImplementedError

    def get_queryset(
        self,
        bounds: GeoBounds | None = None,
        filters: MapFilters | None = None,
    ) -> QuerySet:
        """Get optimized queryset for this location type."""
        raise NotImplementedError

    def bulk_convert(self, queryset: QuerySet) -> list[UnifiedLocation]:
        """Convert multiple location objects efficiently."""
        unified_locations = []
        for obj in queryset:
            unified_loc = self.to_unified_location(obj)
            if unified_loc:
                unified_locations.append(unified_loc)
        return unified_locations


class ParkLocationAdapter(BaseLocationAdapter):
    """Converts Park/ParkLocation to UnifiedLocation."""

    def to_unified_location(self, location_obj: ParkLocation) -> UnifiedLocation | None:
        """Convert ParkLocation to UnifiedLocation."""
        if not location_obj.point or location_obj.latitude is None or location_obj.longitude is None:
            return None

        park = location_obj.park

        return UnifiedLocation(
            id=f"park_{park.id}",
            type=LocationType.PARK,
            name=park.name,
            coordinates=[float(location_obj.latitude), float(location_obj.longitude)],
            address=location_obj.formatted_address,
            metadata={
                "status": getattr(park, "status", "UNKNOWN"),
                "rating": (
                    float(park.average_rating) if hasattr(park, "average_rating") and park.average_rating else None
                ),
                "ride_count": getattr(park, "ride_count", 0),
                "coaster_count": getattr(park, "coaster_count", 0),
                "operator": (park.operator.name if hasattr(park, "operator") and park.operator else None),
                "city": location_obj.city,
                "state": location_obj.state,
                "country": location_obj.country,
            },
            type_data={
                "slug": park.slug,
                "opening_date": (
                    park.opening_date.isoformat() if hasattr(park, "opening_date") and park.opening_date else None
                ),
                "website": getattr(park, "website", ""),
                "operating_season": getattr(park, "operating_season", ""),
                "highway_exit": location_obj.highway_exit,
                "parking_notes": location_obj.parking_notes,
                "best_arrival_time": (
                    location_obj.best_arrival_time.strftime("%H:%M") if location_obj.best_arrival_time else None
                ),
                "seasonal_notes": location_obj.seasonal_notes,
                "url": self._get_park_url(park),
            },
            cluster_weight=self._calculate_park_weight(park),
            cluster_category=self._get_park_category(park),
        )

    def get_queryset(
        self,
        bounds: GeoBounds | None = None,
        filters: MapFilters | None = None,
    ) -> QuerySet:
        """Get optimized queryset for park locations."""
        queryset = ParkLocation.objects.select_related("park", "park__operator").filter(point__isnull=False)

        # Spatial filtering
        if bounds:
            queryset = queryset.filter(point__within=bounds.to_polygon())

        # Park-specific filters
        if filters:
            if filters.park_status:
                queryset = queryset.filter(park__status__in=filters.park_status)
            if filters.search_query:
                queryset = queryset.filter(park__name__icontains=filters.search_query)
            if filters.country:
                queryset = queryset.filter(country=filters.country)
            if filters.state:
                queryset = queryset.filter(state=filters.state)
            if filters.city:
                queryset = queryset.filter(city=filters.city)

        return queryset.order_by("park__name")

    def _calculate_park_weight(self, park) -> int:
        """Calculate clustering weight based on park importance."""
        weight = 1
        if hasattr(park, "ride_count") and park.ride_count and park.ride_count > 20:
            weight += 2
        if hasattr(park, "coaster_count") and park.coaster_count and park.coaster_count > 5:
            weight += 1
        if hasattr(park, "average_rating") and park.average_rating and park.average_rating > 4.0:
            weight += 1
        return min(weight, 5)  # Cap at 5

    def _get_park_category(self, park) -> str:
        """Determine park category for clustering."""
        coaster_count = getattr(park, "coaster_count", 0) or 0
        ride_count = getattr(park, "ride_count", 0) or 0

        if coaster_count >= 10:
            return "major_park"
        elif ride_count >= 15:
            return "theme_park"
        else:
            return "small_park"

    def _get_park_url(self, park) -> str:
        """Get URL for park detail page."""
        try:
            return reverse("parks:detail", kwargs={"slug": park.slug})
        except BaseException:
            return f"/parks/{park.slug}/"


class RideLocationAdapter(BaseLocationAdapter):
    """Converts Ride/RideLocation to UnifiedLocation."""

    def to_unified_location(self, location_obj: RideLocation) -> UnifiedLocation | None:
        """Convert RideLocation to UnifiedLocation."""
        if not location_obj.point or location_obj.latitude is None or location_obj.longitude is None:
            return None

        ride = location_obj.ride

        return UnifiedLocation(
            id=f"ride_{ride.id}",
            type=LocationType.RIDE,
            name=ride.name,
            coordinates=[float(location_obj.latitude), float(location_obj.longitude)],
            address=(f"{location_obj.park_area}, {ride.park.name}" if location_obj.park_area else ride.park.name),
            metadata={
                "park_id": ride.park.id,
                "park_name": ride.park.name,
                "park_area": location_obj.park_area,
                "ride_type": getattr(ride, "ride_type", "Unknown"),
                "status": getattr(ride, "status", "UNKNOWN"),
                "rating": (
                    float(ride.average_rating) if hasattr(ride, "average_rating") and ride.average_rating else None
                ),
                "manufacturer": (
                    getattr(ride, "manufacturer", {}).get("name") if hasattr(ride, "manufacturer") else None
                ),
            },
            type_data={
                "slug": ride.slug,
                "opening_date": (
                    ride.opening_date.isoformat() if hasattr(ride, "opening_date") and ride.opening_date else None
                ),
                "height_requirement": getattr(ride, "height_requirement", ""),
                "duration_minutes": getattr(ride, "duration_minutes", None),
                "max_speed_mph": getattr(ride, "max_speed_mph", None),
                "entrance_notes": location_obj.entrance_notes,
                "accessibility_notes": location_obj.accessibility_notes,
                "url": self._get_ride_url(ride),
            },
            cluster_weight=self._calculate_ride_weight(ride),
            cluster_category=self._get_ride_category(ride),
        )

    def get_queryset(
        self,
        bounds: GeoBounds | None = None,
        filters: MapFilters | None = None,
    ) -> QuerySet:
        """Get optimized queryset for ride locations."""
        queryset = RideLocation.objects.select_related("ride", "ride__park", "ride__park__operator").filter(
            point__isnull=False
        )

        # Spatial filtering
        if bounds:
            queryset = queryset.filter(point__within=bounds.to_polygon())

        # Ride-specific filters
        if filters:
            if filters.ride_types:
                queryset = queryset.filter(ride__ride_type__in=filters.ride_types)
            if filters.search_query:
                queryset = queryset.filter(ride__name__icontains=filters.search_query)

        return queryset.order_by("ride__name")

    def _calculate_ride_weight(self, ride) -> int:
        """Calculate clustering weight based on ride importance."""
        weight = 1
        ride_type = getattr(ride, "ride_type", "").lower()
        if "coaster" in ride_type or "roller" in ride_type:
            weight += 1
        if hasattr(ride, "average_rating") and ride.average_rating and ride.average_rating > 4.0:
            weight += 1
        return min(weight, 3)  # Cap at 3 for rides

    def _get_ride_category(self, ride) -> str:
        """Determine ride category for clustering."""
        ride_type = getattr(ride, "ride_type", "").lower()
        if "coaster" in ride_type or "roller" in ride_type:
            return "coaster"
        elif "water" in ride_type or "splash" in ride_type:
            return "water_ride"
        else:
            return "other_ride"

    def _get_ride_url(self, ride) -> str:
        """Get URL for ride detail page."""
        try:
            return reverse("rides:detail", kwargs={"slug": ride.slug})
        except BaseException:
            return f"/rides/{ride.slug}/"


class CompanyLocationAdapter(BaseLocationAdapter):
    """Converts Company/CompanyHeadquarters to UnifiedLocation."""

    def to_unified_location(self, location_obj: CompanyHeadquarters) -> UnifiedLocation | None:
        """Convert CompanyHeadquarters to UnifiedLocation."""
        # Note: CompanyHeadquarters doesn't have coordinates, so we need to geocode
        # For now, we'll skip companies without coordinates
        # See FUTURE_WORK.md - THRILLWIKI-101 for geocoding implementation plan
        return None

    def get_queryset(
        self,
        bounds: GeoBounds | None = None,
        filters: MapFilters | None = None,
    ) -> QuerySet:
        """Get optimized queryset for company locations."""
        queryset = CompanyHeadquarters.objects.select_related("company")

        # Company-specific filters
        if filters:
            if filters.company_roles:
                queryset = queryset.filter(company__roles__overlap=filters.company_roles)
            if filters.search_query:
                queryset = queryset.filter(company__name__icontains=filters.search_query)
            if filters.country:
                queryset = queryset.filter(country=filters.country)
            if filters.city:
                queryset = queryset.filter(city=filters.city)

        return queryset.order_by("company__name")


# GenericLocationAdapter removed - generic location app is being deprecated
# All location functionality moved to domain-specific models (ParkLocation, RideLocation, etc.)


class LocationAbstractionLayer:
    """
    Abstraction layer handling different location model types.
    Implements the adapter pattern to provide unified access to all location types.
    """

    def __init__(self):
        self.adapters = {
            LocationType.PARK: ParkLocationAdapter(),
            LocationType.RIDE: RideLocationAdapter(),
            LocationType.COMPANY: CompanyLocationAdapter(),
            # LocationType.GENERIC: Removed - generic location app deprecated
        }

    def get_all_locations(
        self,
        bounds: GeoBounds | None = None,
        filters: MapFilters | None = None,
    ) -> list[UnifiedLocation]:
        """Get locations from all sources within bounds."""
        all_locations = []

        # Determine which location types to include
        location_types = filters.location_types if filters and filters.location_types else set(LocationType)

        for location_type in location_types:
            adapter = self.adapters[location_type]
            queryset = adapter.get_queryset(bounds, filters)
            locations = adapter.bulk_convert(queryset)
            all_locations.extend(locations)

        return all_locations

    def get_locations_by_type(
        self,
        location_type: LocationType,
        bounds: GeoBounds | None = None,
        filters: MapFilters | None = None,
    ) -> list[UnifiedLocation]:
        """Get locations of specific type."""
        adapter = self.adapters[location_type]
        queryset = adapter.get_queryset(bounds, filters)
        return adapter.bulk_convert(queryset)

    def get_location_by_id(self, location_type: LocationType, location_id: int) -> UnifiedLocation | None:
        """Get single location with full details."""
        adapter = self.adapters[location_type]

        try:
            if location_type == LocationType.PARK:
                obj = ParkLocation.objects.select_related("park", "park__operator").get(park_id=location_id)
            elif location_type == LocationType.RIDE:
                obj = RideLocation.objects.select_related("ride", "ride__park").get(ride_id=location_id)
            elif location_type == LocationType.COMPANY:
                obj = CompanyHeadquarters.objects.select_related("company").get(company_id=location_id)
            # LocationType.GENERIC removed - generic location app deprecated
            else:
                return None

            return adapter.to_unified_location(obj)
        except Exception:
            return None


# Import models after defining adapters to avoid circular imports
