"""
Location-aware search service for ThrillWiki.

Integrates PostGIS location data with existing search functionality
to provide proximity-based search, location filtering, and geographic
search capabilities.
"""

from dataclasses import dataclass
from typing import Any

from django.contrib.gis.geos import Point
from django.contrib.gis.measure import Distance
from django.db.models import Q

from apps.parks.models import Company, Park, ParkLocation
from apps.rides.models import Ride


@dataclass
class LocationSearchFilters:
    """Filters for location-aware search queries."""

    # Text search
    search_query: str | None = None

    # Location-based filters
    location_point: Point | None = None
    radius_km: float | None = None
    location_types: set[str] | None = None  # 'park', 'ride', 'company'

    # Geographic filters
    country: str | None = None
    state: str | None = None
    city: str | None = None

    # Content-specific filters
    park_status: list[str] | None = None
    ride_types: list[str] | None = None
    company_roles: list[str] | None = None

    # Result options
    include_distance: bool = True
    max_results: int = 100


@dataclass
class LocationSearchResult:
    """Single search result with location data."""

    # Core data
    content_type: str  # 'park', 'ride', 'company'
    object_id: int
    name: str
    description: str | None = None
    url: str | None = None

    # Location data
    latitude: float | None = None
    longitude: float | None = None
    address: str | None = None
    city: str | None = None
    state: str | None = None
    country: str | None = None

    # Distance data (if proximity search)
    distance_km: float | None = None

    # Additional metadata
    status: str | None = None
    tags: list[str] | None = None
    rating: float | None = None

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for JSON serialization."""
        return {
            "content_type": self.content_type,
            "object_id": self.object_id,
            "name": self.name,
            "description": self.description,
            "url": self.url,
            "location": {
                "latitude": self.latitude,
                "longitude": self.longitude,
                "address": self.address,
                "city": self.city,
                "state": self.state,
                "country": self.country,
            },
            "distance_km": self.distance_km,
            "status": self.status,
            "tags": self.tags or [],
            "rating": self.rating,
        }


class LocationSearchService:
    """Service for performing location-aware searches across ThrillWiki content."""

    def search(self, filters: LocationSearchFilters) -> list[LocationSearchResult]:
        """
        Perform a comprehensive location-aware search.

        Args:
            filters: Search filters and options

        Returns:
            List of search results with location data
        """
        results = []

        # Search each content type based on filters
        if not filters.location_types or "park" in filters.location_types:
            results.extend(self._search_parks(filters))

        if not filters.location_types or "ride" in filters.location_types:
            results.extend(self._search_rides(filters))

        if not filters.location_types or "company" in filters.location_types:
            results.extend(self._search_companies(filters))

        # Sort by distance if proximity search, otherwise by relevance
        if filters.location_point and filters.include_distance:
            results.sort(key=lambda x: x.distance_km or float("inf"))
        else:
            results.sort(key=lambda x: x.name.lower())

        # Apply max results limit
        return results[: filters.max_results]

    def _search_parks(self, filters: LocationSearchFilters) -> list[LocationSearchResult]:
        """Search parks with location data."""
        queryset = Park.objects.select_related("location", "operator").all()

        # Apply location filters
        queryset = self._apply_location_filters(queryset, filters, "location__point")

        # Apply text search
        if filters.search_query:
            query = (
                Q(name__icontains=filters.search_query)
                | Q(description__icontains=filters.search_query)
                | Q(location__city__icontains=filters.search_query)
                | Q(location__state__icontains=filters.search_query)
                | Q(location__country__icontains=filters.search_query)
            )
            queryset = queryset.filter(query)

        # Apply park-specific filters
        if filters.park_status:
            queryset = queryset.filter(status__in=filters.park_status)

        # Add distance annotation if proximity search
        if filters.location_point and filters.include_distance:
            queryset = queryset.annotate(distance=Distance("location__point", filters.location_point)).order_by(
                "distance"
            )

        # Convert to search results
        results = []
        for park in queryset:
            result = LocationSearchResult(
                content_type="park",
                object_id=park.id,
                name=park.name,
                description=park.description,
                url=(park.get_absolute_url() if hasattr(park, "get_absolute_url") else None),
                status=park.get_status_display(),
                rating=(float(park.average_rating) if park.average_rating else None),
                tags=["park", park.status.lower()],
            )

            # Add location data
            if hasattr(park, "location") and park.location:
                location = park.location
                result.latitude = location.latitude
                result.longitude = location.longitude
                result.address = location.formatted_address
                result.city = location.city
                result.state = location.state
                result.country = location.country

                # Add distance if proximity search
                if filters.location_point and filters.include_distance and hasattr(park, "distance"):
                    result.distance_km = float(park.distance.km)

            results.append(result)

        return results

    def _search_rides(self, filters: LocationSearchFilters) -> list[LocationSearchResult]:
        """Search rides with location data."""
        queryset = Ride.objects.select_related("park", "location").all()

        # Apply location filters
        queryset = self._apply_location_filters(queryset, filters, "location__point")

        # Apply text search
        if filters.search_query:
            query = (
                Q(name__icontains=filters.search_query)
                | Q(description__icontains=filters.search_query)
                | Q(park__name__icontains=filters.search_query)
                | Q(location__park_area__icontains=filters.search_query)
            )
            queryset = queryset.filter(query)

        # Apply ride-specific filters
        if filters.ride_types:
            queryset = queryset.filter(ride_type__in=filters.ride_types)

        # Add distance annotation if proximity search
        if filters.location_point and filters.include_distance:
            queryset = queryset.annotate(distance=Distance("location__point", filters.location_point)).order_by(
                "distance"
            )

        # Convert to search results
        results = []
        for ride in queryset:
            result = LocationSearchResult(
                content_type="ride",
                object_id=ride.id,
                name=ride.name,
                description=ride.description,
                url=(ride.get_absolute_url() if hasattr(ride, "get_absolute_url") else None),
                status=ride.status,
                tags=[
                    "ride",
                    ride.ride_type.lower() if ride.ride_type else "attraction",
                ],
            )

            # Add location data from ride location or park location
            location = None
            if hasattr(ride, "location") and ride.location:
                location = ride.location
                result.latitude = location.latitude
                result.longitude = location.longitude
                result.address = f"{ride.park.name} - {location.park_area}" if location.park_area else ride.park.name

                # Add distance if proximity search
                if filters.location_point and filters.include_distance and hasattr(ride, "distance"):
                    result.distance_km = float(ride.distance.km)

            # Fall back to park location if no specific ride location
            elif ride.park and hasattr(ride.park, "location") and ride.park.location:
                park_location = ride.park.location
                result.latitude = park_location.latitude
                result.longitude = park_location.longitude
                result.address = park_location.formatted_address
                result.city = park_location.city
                result.state = park_location.state
                result.country = park_location.country

            results.append(result)

        return results

    def _search_companies(self, filters: LocationSearchFilters) -> list[LocationSearchResult]:
        """Search companies with headquarters location data."""
        queryset = Company.objects.select_related("headquarters").all()

        # Apply location filters
        queryset = self._apply_location_filters(queryset, filters, "headquarters__point")

        # Apply text search
        if filters.search_query:
            query = (
                Q(name__icontains=filters.search_query)
                | Q(description__icontains=filters.search_query)
                | Q(headquarters__city__icontains=filters.search_query)
                | Q(headquarters__state_province__icontains=filters.search_query)
                | Q(headquarters__country__icontains=filters.search_query)
            )
            queryset = queryset.filter(query)

        # Apply company-specific filters
        if filters.company_roles:
            queryset = queryset.filter(roles__overlap=filters.company_roles)

        # Add distance annotation if proximity search
        if filters.location_point and filters.include_distance:
            queryset = queryset.annotate(distance=Distance("headquarters__point", filters.location_point)).order_by(
                "distance"
            )

        # Convert to search results
        results = []
        for company in queryset:
            result = LocationSearchResult(
                content_type="company",
                object_id=company.id,
                name=company.name,
                description=company.description,
                url=(company.get_absolute_url() if hasattr(company, "get_absolute_url") else None),
                tags=["company"] + (company.roles or []),
            )

            # Add location data
            if hasattr(company, "headquarters") and company.headquarters:
                hq = company.headquarters
                result.latitude = hq.latitude
                result.longitude = hq.longitude
                result.address = hq.formatted_address
                result.city = hq.city
                result.state = hq.state_province
                result.country = hq.country

                # Add distance if proximity search
                if filters.location_point and filters.include_distance and hasattr(company, "distance"):
                    result.distance_km = float(company.distance.km)

            results.append(result)

        return results

    def _apply_location_filters(self, queryset, filters: LocationSearchFilters, point_field: str):
        """Apply common location filters to a queryset."""

        # Proximity filter
        if filters.location_point and filters.radius_km:
            distance = Distance(km=filters.radius_km)
            queryset = queryset.filter(
                **{
                    f"{point_field}__distance_lte": (
                        filters.location_point,
                        distance,
                    )
                }
            )

        # Geographic filters - adjust field names based on model
        if filters.country:
            if "headquarters" in point_field:
                queryset = queryset.filter(headquarters__country__icontains=filters.country)
            else:
                location_field = point_field.split("__")[0]
                queryset = queryset.filter(**{f"{location_field}__country__icontains": filters.country})

        if filters.state:
            if "headquarters" in point_field:
                queryset = queryset.filter(headquarters__state_province__icontains=filters.state)
            else:
                location_field = point_field.split("__")[0]
                queryset = queryset.filter(**{f"{location_field}__state__icontains": filters.state})

        if filters.city:
            location_field = point_field.split("__")[0]
            queryset = queryset.filter(**{f"{location_field}__city__icontains": filters.city})

        return queryset

    def suggest_locations(self, query: str, limit: int = 10) -> list[dict[str, Any]]:
        """
        Get location suggestions for autocomplete.

        Args:
            query: Search query string
            limit: Maximum number of suggestions

        Returns:
            List of location suggestions
        """
        suggestions = []

        if len(query) < 2:
            return suggestions

        # Get park location suggestions
        park_locations = ParkLocation.objects.filter(
            Q(park__name__icontains=query) | Q(city__icontains=query) | Q(state__icontains=query)
        ).select_related("park")[: limit // 3]

        for location in park_locations:
            suggestions.append(
                {
                    "type": "park",
                    "name": location.park.name,
                    "address": location.formatted_address,
                    "coordinates": location.coordinates,
                    "url": (location.park.get_absolute_url() if hasattr(location.park, "get_absolute_url") else None),
                }
            )

        # Get city suggestions
        cities = (
            ParkLocation.objects.filter(city__icontains=query)
            .values("city", "state", "country")
            .distinct()[: limit // 3]
        )

        for city_data in cities:
            suggestions.append(
                {
                    "type": "city",
                    "name": f"{city_data['city']}, {city_data['state']}",
                    "address": f"{city_data['city']}, {city_data['state']}, {
                        city_data['country']
                    }",
                    "coordinates": None,
                }
            )

        return suggestions[:limit]


# Global instance
location_search_service = LocationSearchService()
