"""
Rides-specific location services with OpenStreetMap integration.
Handles location management for individual rides within parks.
"""

import logging
from typing import Any

import requests
from django.db import transaction

from ..models import RideLocation

logger = logging.getLogger(__name__)


class RideLocationService:
    """
    Location service specifically for rides using OpenStreetMap integration.
    Focuses on precise positioning within parks and navigation assistance.
    """

    NOMINATIM_BASE_URL = "https://nominatim.openstreetmap.org"
    USER_AGENT = "ThrillWiki/1.0 (https://thrillwiki.com)"

    @classmethod
    def create_ride_location(
        cls,
        *,
        ride,
        latitude: float | None = None,
        longitude: float | None = None,
        park_area: str = "",
        notes: str = "",
        entrance_notes: str = "",
        accessibility_notes: str = "",
    ) -> RideLocation:
        """
        Create a location for a ride within a park.

        Args:
            ride: Ride instance
            latitude: Latitude coordinate (optional for rides)
            longitude: Longitude coordinate (optional for rides)
            park_area: Themed area within the park
            notes: General location notes
            entrance_notes: Entrance and navigation notes
            accessibility_notes: Accessibility information

        Returns:
            Created RideLocation instance
        """
        with transaction.atomic():
            ride_location = RideLocation(
                ride=ride,
                park_area=park_area,
                notes=notes,
                entrance_notes=entrance_notes,
                accessibility_notes=accessibility_notes,
            )

            # Set coordinates if provided
            if latitude is not None and longitude is not None:
                ride_location.set_coordinates(latitude, longitude)

            ride_location.full_clean()
            ride_location.save()

            return ride_location

    @classmethod
    def update_ride_location(cls, ride_location: RideLocation, **updates) -> RideLocation:
        """
        Update ride location with validation.

        Args:
            ride_location: RideLocation instance to update
            **updates: Fields to update

        Returns:
            Updated RideLocation instance
        """
        with transaction.atomic():
            # Handle coordinates separately
            latitude = updates.pop("latitude", None)
            longitude = updates.pop("longitude", None)

            # Update regular fields
            for field, value in updates.items():
                if hasattr(ride_location, field):
                    setattr(ride_location, field, value)

            # Update coordinates if provided
            if latitude is not None and longitude is not None:
                ride_location.set_coordinates(latitude, longitude)

            ride_location.full_clean()
            ride_location.save()

            return ride_location

    @classmethod
    def find_rides_in_area(cls, park, park_area: str) -> list[RideLocation]:
        """
        Find all rides in a specific park area.

        Args:
            park: Park instance
            park_area: Name of the park area/land

        Returns:
            List of RideLocation instances in the area
        """
        return list(
            RideLocation.objects.filter(ride__park=park, park_area__icontains=park_area)
            .select_related("ride")
            .order_by("ride__name")
        )

    @classmethod
    def find_nearby_rides(
        cls, latitude: float, longitude: float, park=None, radius_meters: float = 500
    ) -> list[RideLocation]:
        """
        Find rides near given coordinates using PostGIS.
        Useful for finding rides near a specific location within a park.

        Args:
            latitude: Center latitude
            longitude: Center longitude
            park: Optional park to limit search to
            radius_meters: Search radius in meters (default: 500m)

        Returns:
            List of nearby RideLocation instances
        """
        from django.contrib.gis.geos import Point
        from django.contrib.gis.measure import Distance

        center_point = Point(longitude, latitude, srid=4326)

        queryset = RideLocation.objects.filter(
            point__distance_lte=(center_point, Distance(m=radius_meters)),
            point__isnull=False,
        )

        if park:
            queryset = queryset.filter(ride__park=park)

        return list(queryset.select_related("ride", "ride__park").order_by("point__distance"))

    @classmethod
    def get_ride_navigation_info(cls, ride_location: RideLocation) -> dict[str, Any]:
        """
        Get comprehensive navigation information for a ride.

        Args:
            ride_location: RideLocation instance

        Returns:
            Dictionary with navigation information
        """
        info = {
            "ride_name": ride_location.ride.name,
            "park_name": ride_location.ride.park.name,
            "park_area": ride_location.park_area,
            "has_coordinates": ride_location.has_coordinates,
            "entrance_notes": ride_location.entrance_notes,
            "accessibility_notes": ride_location.accessibility_notes,
            "general_notes": ride_location.notes,
        }

        # Add coordinate information if available
        if ride_location.has_coordinates:
            info.update(
                {
                    "latitude": ride_location.latitude,
                    "longitude": ride_location.longitude,
                    "coordinates": ride_location.coordinates,
                }
            )

            # Calculate distance to park entrance if park has location
            park_location = getattr(ride_location.ride.park, "location", None)
            if park_location and park_location.point:
                distance_km = ride_location.distance_to_park_location()
                if distance_km is not None:
                    info["distance_from_park_entrance_km"] = round(distance_km, 2)

        return info

    @classmethod
    def estimate_ride_coordinates_from_park(
        cls,
        ride_location: RideLocation,
        area_offset_meters: dict[str, list[float]] | None = None,
    ) -> list[float] | None:
        """
        Estimate ride coordinates based on park location and area.
        Useful when exact ride coordinates are not available.

        Args:
            ride_location: RideLocation instance
            area_offset_meters: Dictionary mapping area names to [north_offset, east_offset] in meters

        Returns:
            Estimated [latitude, longitude] list or None
        """
        park_location = getattr(ride_location.ride.park, "location", None)
        if not park_location or not park_location.point:
            return None

        # Default area offsets (rough estimates for common themed areas)
        default_offsets = {
            "main street": (0, 0),  # Usually at entrance
            "fantasyland": (200, 100),  # Often north-east
            "tomorrowland": (100, 200),  # Often east
            "frontierland": (-100, -200),  # Often south-west
            "adventureland": (-200, 100),  # Often south-east
            "new orleans square": (-150, -100),
            "critter country": (-200, -200),
            "galaxy's edge": (300, 300),  # Often on periphery
            "cars land": (200, -200),
            "pixar pier": (0, 300),  # Often waterfront
        }

        offsets = area_offset_meters or default_offsets

        # Find matching area offset
        area_lower = ride_location.park_area.lower()
        offset = None

        for area_name, area_offset in offsets.items():
            if area_name in area_lower:
                offset = area_offset
                break

        if not offset:
            # Default small random offset if no specific area match
            import random

            offset = (random.randint(-100, 100), random.randint(-100, 100))

        # Convert meter offsets to coordinate offsets
        # Rough conversion: 1 degree latitude ≈ 111,000 meters
        # 1 degree longitude varies by latitude, but we'll use a rough approximation
        lat_offset = offset[0] / 111000  # North offset in degrees
        lon_offset = offset[1] / (111000 * abs(park_location.latitude) * 0.01)  # East offset

        estimated_lat = park_location.latitude + lat_offset
        estimated_lon = park_location.longitude + lon_offset

        return [estimated_lat, estimated_lon]

    @classmethod
    def bulk_update_ride_areas_from_osm(cls, park) -> int:
        """
        Bulk update ride locations for a park using OSM data.
        Attempts to find more precise locations for rides within the park.

        Args:
            park: Park instance

        Returns:
            Number of ride locations updated
        """
        updated_count = 0
        park_location = getattr(park, "location", None)

        if not park_location or not park_location.point:
            return updated_count

        # Get all rides in the park that don't have precise coordinates
        ride_locations = RideLocation.objects.filter(ride__park=park, point__isnull=True).select_related("ride")

        for ride_location in ride_locations:
            # Try to search for the specific ride within the park area
            search_query = f"{ride_location.ride.name} {park.name}"

            try:
                # Search for the ride specifically
                params = {
                    "q": search_query,
                    "format": "json",
                    "limit": 5,
                    "addressdetails": 1,
                    "bounded": 1,  # Restrict to viewbox
                    # Create a bounding box around the park (roughly 2km radius)
                    "viewbox": f"{park_location.longitude - 0.02},{park_location.latitude + 0.02},{park_location.longitude + 0.02},{park_location.latitude - 0.02}",
                }

                headers = {"User-Agent": cls.USER_AGENT}

                response = requests.get(
                    f"{cls.NOMINATIM_BASE_URL}/search",
                    params=params,
                    headers=headers,
                    timeout=5,
                )

                if response.status_code == 200:
                    results = response.json()

                    # Look for results that might be the ride
                    for result in results:
                        display_name = result.get("display_name", "").lower()
                        if ride_location.ride.name.lower() in display_name and park.name.lower() in display_name:
                            # Update the ride location
                            ride_location.set_coordinates(float(result["lat"]), float(result["lon"]))
                            ride_location.save()
                            updated_count += 1
                            break

            except Exception as e:
                logger.warning(f"Error updating ride location for {ride_location.ride.name}: {str(e)}")
                continue

        return updated_count

    @classmethod
    def generate_park_area_map(cls, park) -> dict[str, list[str]]:
        """
        Generate a map of park areas and the rides in each area.

        Args:
            park: Park instance

        Returns:
            Dictionary mapping area names to lists of ride names
        """
        area_map = {}

        ride_locations = (
            RideLocation.objects.filter(ride__park=park).select_related("ride").order_by("park_area", "ride__name")
        )

        for ride_location in ride_locations:
            area = ride_location.park_area or "Unknown Area"
            if area not in area_map:
                area_map[area] = []
            area_map[area].append(ride_location.ride.name)

        return area_map
