"""
Centralized map API views.
Migrated from apps.core.views.map_views

Caching Strategy:
- MapLocationsAPIView: 5 minutes (300s) - map data changes infrequently but needs freshness
- MapLocationDetailAPIView: 30 minutes (1800s) - detail views are stable
- MapSearchAPIView: 5 minutes (300s) - search results should be consistent
- MapBoundsAPIView: 5 minutes (300s) - bounds queries are location-specific
- MapStatsAPIView: 10 minutes (600s) - stats are aggregated and change slowly
"""

import logging

from django.contrib.gis.geos import Polygon
from django.core.cache import cache
from django.db.models import Q
from django.http import HttpRequest
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import (
    OpenApiExample,
    OpenApiParameter,
    extend_schema,
    extend_schema_view,
)
from rest_framework import status
from rest_framework.permissions import AllowAny, IsAdminUser
from rest_framework.response import Response
from rest_framework.views import APIView

from apps.core.decorators.cache_decorators import cache_api_response
from apps.core.services.enhanced_cache_service import EnhancedCacheService
from apps.parks.models import Park
from apps.rides.models import Ride

from ..serializers.maps import (
    MapLocationDetailSerializer,
    MapLocationsResponseSerializer,
    MapSearchResponseSerializer,
)
from apps.core.utils import capture_and_log

logger = logging.getLogger(__name__)


@extend_schema_view(
    get=extend_schema(
        summary="Get map locations",
        description="Get map locations with optional clustering and filtering.",
        parameters=[
            OpenApiParameter(
                "north",
                type=OpenApiTypes.NUMBER,
                location=OpenApiParameter.QUERY,
                required=False,
                description="Northern latitude bound (-90 to 90). Used with south, east, west to define geographic bounds.",
                examples=[OpenApiExample("Example", value=41.5)],
            ),
            OpenApiParameter(
                "south",
                type=OpenApiTypes.NUMBER,
                location=OpenApiParameter.QUERY,
                required=False,
                description="Southern latitude bound (-90 to 90). Must be less than north bound.",
                examples=[OpenApiExample("Example", value=41.4)],
            ),
            OpenApiParameter(
                "east",
                type=OpenApiTypes.NUMBER,
                location=OpenApiParameter.QUERY,
                required=False,
                description="Eastern longitude bound (-180 to 180). Must be greater than west bound.",
                examples=[OpenApiExample("Example", value=-82.6)],
            ),
            OpenApiParameter(
                "west",
                type=OpenApiTypes.NUMBER,
                location=OpenApiParameter.QUERY,
                required=False,
                description="Western longitude bound (-180 to 180). Used with other bounds for geographic filtering.",
                examples=[OpenApiExample("Example", value=-82.8)],
            ),
            OpenApiParameter(
                "zoom",
                type=OpenApiTypes.INT,
                location=OpenApiParameter.QUERY,
                required=False,
                description="Map zoom level (1-20). Higher values show more detail. Used for clustering decisions.",
                examples=[OpenApiExample("Example", value=10)],
            ),
            OpenApiParameter(
                "types",
                type=OpenApiTypes.STR,
                location=OpenApiParameter.QUERY,
                required=False,
                description="Comma-separated location types to include. Valid values: 'park', 'ride'. Default: 'park,ride'",
                examples=[
                    OpenApiExample("All types", value="park,ride"),
                    OpenApiExample("Parks only", value="park"),
                    OpenApiExample("Rides only", value="ride"),
                ],
            ),
            OpenApiParameter(
                "cluster",
                type=OpenApiTypes.BOOL,
                location=OpenApiParameter.QUERY,
                required=False,
                description="Enable location clustering for high-density areas. Default: false",
                examples=[
                    OpenApiExample("Enable clustering", value=True),
                    OpenApiExample("Disable clustering", value=False),
                ],
            ),
            OpenApiParameter(
                "q",
                type=OpenApiTypes.STR,
                location=OpenApiParameter.QUERY,
                required=False,
                description="Text search query. Searches park/ride names, cities, and states.",
                examples=[
                    OpenApiExample("Park name", value="Cedar Point"),
                    OpenApiExample("Ride type", value="roller coaster"),
                    OpenApiExample("Location", value="Ohio"),
                ],
            ),
        ],
        responses={
            200: MapLocationsResponseSerializer,
            400: OpenApiTypes.OBJECT,
            500: OpenApiTypes.OBJECT,
        },
        tags=["Maps"],
    ),
)
class MapLocationsAPIView(APIView):
    """API endpoint for getting map locations with optional clustering."""

    permission_classes = [AllowAny]

    def _parse_request_parameters(self, request: HttpRequest) -> dict:
        """Parse and validate request parameters."""
        return {
            "north": request.GET.get("north"),
            "south": request.GET.get("south"),
            "east": request.GET.get("east"),
            "west": request.GET.get("west"),
            "zoom": request.GET.get("zoom", 10),
            "types": request.GET.get("types", "park,ride").split(","),
            "cluster": request.GET.get("cluster", "false").lower() == "true",
            "query": request.GET.get("q", "").strip(),
        }

    def _build_cache_key(self, params: dict) -> str:
        """Build cache key from parameters."""
        return (
            f"map_locations_{params['north']}_{params['south']}_"
            f"{params['east']}_{params['west']}_{params['zoom']}_"
            f"{','.join(params['types'])}_{params['cluster']}_{params['query']}"
        )

    def _create_bounds_polygon(self, north: str, south: str, east: str, west: str) -> Polygon | None:
        """Create bounds polygon from coordinate strings."""
        if not all([north, south, east, west]):
            return None
        try:
            return Polygon.from_bbox((float(west), float(south), float(east), float(north)))
        except (ValueError, TypeError):
            return None

    def _serialize_park_location(self, park) -> dict:
        """Serialize park location data."""
        location = park.location if hasattr(park, "location") and park.location else None
        return {
            "city": location.city if location else "",
            "state": location.state if location else "",
            "country": location.country if location else "",
            "formatted_address": location.formatted_address if location else "",
        }

    def _serialize_park_data(self, park) -> dict:
        """Serialize park data for map response."""
        location = park.location if hasattr(park, "location") and park.location else None
        return {
            "id": park.id,
            "type": "park",
            "name": park.name,
            "slug": park.slug,
            "latitude": location.latitude if location else None,
            "longitude": location.longitude if location else None,
            "status": park.status,
            "location": self._serialize_park_location(park),
            "stats": {
                "coaster_count": park.coaster_count or 0,
                "ride_count": park.ride_count or 0,
                "average_rating": (float(park.average_rating) if park.average_rating else None),
            },
        }

    def _get_parks_data(self, params: dict) -> list:
        """Get and serialize parks data."""
        if "park" not in params["types"]:
            return []

        parks_query = Park.objects.select_related("location", "operator").filter(location__point__isnull=False)

        # Apply bounds filtering
        bounds_polygon = self._create_bounds_polygon(params["north"], params["south"], params["east"], params["west"])
        if bounds_polygon:
            parks_query = parks_query.filter(location__point__within=bounds_polygon)

        # Apply text search
        if params["query"]:
            parks_query = parks_query.filter(
                Q(name__icontains=params["query"])
                | Q(location__city__icontains=params["query"])
                | Q(location__state__icontains=params["query"])
            )

        return [self._serialize_park_data(park) for park in parks_query[:100]]

    def _serialize_ride_location(self, ride) -> dict:
        """Serialize ride location data."""
        location = ride.park.location if hasattr(ride.park, "location") and ride.park.location else None
        return {
            "city": location.city if location else "",
            "state": location.state if location else "",
            "country": location.country if location else "",
            "formatted_address": location.formatted_address if location else "",
        }

    def _serialize_ride_data(self, ride) -> dict:
        """Serialize ride data for map response."""
        location = ride.park.location if hasattr(ride.park, "location") and ride.park.location else None
        return {
            "id": ride.id,
            "type": "ride",
            "name": ride.name,
            "slug": ride.slug,
            "latitude": location.latitude if location else None,
            "longitude": location.longitude if location else None,
            "status": ride.status,
            "location": self._serialize_ride_location(ride),
            "stats": {
                "category": ride.get_category_display() if ride.category else None,
                "average_rating": (float(ride.average_rating) if ride.average_rating else None),
                "park_name": ride.park.name,
            },
        }

    def _get_rides_data(self, params: dict) -> list:
        """Get and serialize rides data."""
        if "ride" not in params["types"]:
            return []

        rides_query = Ride.objects.select_related("park__location", "manufacturer").filter(
            park__location__point__isnull=False
        )

        # Apply bounds filtering
        bounds_polygon = self._create_bounds_polygon(params["north"], params["south"], params["east"], params["west"])
        if bounds_polygon:
            rides_query = rides_query.filter(park__location__point__within=bounds_polygon)

        # Apply text search
        if params["query"]:
            rides_query = rides_query.filter(
                Q(name__icontains=params["query"])
                | Q(park__name__icontains=params["query"])
                | Q(park__location__city__icontains=params["query"])
            )

        return [self._serialize_ride_data(ride) for ride in rides_query[:100]]

    def _calculate_bounds(self, locations: list) -> dict:
        """Calculate bounds from location results."""
        if not locations:
            return {}

        lats = [loc["latitude"] for loc in locations if loc["latitude"]]
        lngs = [loc["longitude"] for loc in locations if loc["longitude"]]

        if not lats or not lngs:
            return {}

        return {
            "north": max(lats),
            "south": min(lats),
            "east": max(lngs),
            "west": min(lngs),
        }

    def _build_response(self, locations: list, params: dict) -> dict:
        """Build the final response data."""
        return {
            "status": "success",
            "locations": locations,
            "clusters": [],  # See FUTURE_WORK.md - THRILLWIKI-106 for implementation plan
            "bounds": self._calculate_bounds(locations),
            "total_count": len(locations),
            "clustered": params["cluster"],
        }

    def get(self, request: HttpRequest) -> Response:
        """
        Get map locations with optional clustering and filtering.

        Caching: Uses EnhancedCacheService with 5-minute timeout (300s).
        Cache key is based on all query parameters for proper invalidation.
        """
        try:
            params = self._parse_request_parameters(request)
            cache_key = self._build_cache_key(params)

            # Use EnhancedCacheService for improved caching with monitoring
            cache_service = EnhancedCacheService()
            cached_result = cache_service.get_cached_api_response("map_locations", params)
            if cached_result:
                logger.debug(f"Cache hit for map_locations with key: {cache_key}")
                return Response(cached_result)

            # Get location data
            parks_data = self._get_parks_data(params)
            rides_data = self._get_rides_data(params)
            locations = parks_data + rides_data

            # Build response
            result = self._build_response(locations, params)

            # Cache result for 5 minutes using EnhancedCacheService
            cache_service.cache_api_response("map_locations", params, result, timeout=300)
            logger.debug(f"Cached map_locations result for key: {cache_key}")

            return Response(result)

        except Exception as e:
            capture_and_log(e, 'Get map locations', source='api')
            return Response(
                {"status": "error", "detail": "Failed to retrieve map locations"},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


@extend_schema_view(
    get=extend_schema(
        summary="Get location details",
        description="Get detailed information about a specific location.",
        parameters=[
            OpenApiParameter(
                "location_type",
                type=OpenApiTypes.STR,
                location=OpenApiParameter.PATH,
                required=True,
                description="Type of location",
            ),
            OpenApiParameter(
                "location_id",
                type=OpenApiTypes.INT,
                location=OpenApiParameter.PATH,
                required=True,
                description="ID of the location",
            ),
        ],
        responses={
            200: MapLocationDetailSerializer,
            400: OpenApiTypes.OBJECT,
            404: OpenApiTypes.OBJECT,
            500: OpenApiTypes.OBJECT,
        },
        tags=["Maps"],
    ),
)
class MapLocationDetailAPIView(APIView):
    """
    API endpoint for getting detailed information about a specific location.

    Caching: 30-minute timeout (1800s) - detail views are stable and change infrequently.
    """

    permission_classes = [AllowAny]

    @cache_api_response(timeout=1800, key_prefix="map_detail")
    def get(self, request: HttpRequest, location_type: str, location_id: int) -> Response:
        """Get detailed information for a specific location."""
        try:
            if location_type == "park":
                try:
                    obj = Park.objects.select_related("location", "operator").get(id=location_id)
                except Park.DoesNotExist:
                    return Response(
                        {"status": "error", "detail": "Park not found"},
                        status=status.HTTP_404_NOT_FOUND,
                    )
            elif location_type == "ride":
                try:
                    obj = Ride.objects.select_related("park__location", "manufacturer").get(id=location_id)
                except Ride.DoesNotExist:
                    return Response(
                        {"status": "error", "detail": "Ride not found"},
                        status=status.HTTP_404_NOT_FOUND,
                    )
            else:
                return Response(
                    {"status": "error", "detail": "Invalid location type"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Serialize the object
            if location_type == "park":
                data = {
                    "id": obj.id,
                    "type": "park",
                    "name": obj.name,
                    "slug": obj.slug,
                    "description": obj.description,
                    "latitude": (obj.location.latitude if hasattr(obj, "location") and obj.location else None),
                    "longitude": (obj.location.longitude if hasattr(obj, "location") and obj.location else None),
                    "status": obj.status,
                    "location": {
                        "street_address": (
                            obj.location.street_address if hasattr(obj, "location") and obj.location else ""
                        ),
                        "city": (obj.location.city if hasattr(obj, "location") and obj.location else ""),
                        "state": (obj.location.state if hasattr(obj, "location") and obj.location else ""),
                        "country": (obj.location.country if hasattr(obj, "location") and obj.location else ""),
                        "postal_code": (obj.location.postal_code if hasattr(obj, "location") and obj.location else ""),
                        "formatted_address": (
                            obj.location.formatted_address if hasattr(obj, "location") and obj.location else ""
                        ),
                    },
                    "stats": {
                        "coaster_count": obj.coaster_count or 0,
                        "ride_count": obj.ride_count or 0,
                        "average_rating": (float(obj.average_rating) if obj.average_rating else None),
                        "size_acres": float(obj.size_acres) if obj.size_acres else None,
                        "opening_date": (obj.opening_date.isoformat() if obj.opening_date else None),
                    },
                    "nearby_locations": [],  # See FUTURE_WORK.md - THRILLWIKI-107
                }
            else:  # ride
                data = {
                    "id": obj.id,
                    "type": "ride",
                    "name": obj.name,
                    "slug": obj.slug,
                    "description": obj.description,
                    "latitude": (
                        obj.park.location.latitude if hasattr(obj.park, "location") and obj.park.location else None
                    ),
                    "longitude": (
                        obj.park.location.longitude if hasattr(obj.park, "location") and obj.park.location else None
                    ),
                    "status": obj.status,
                    "location": {
                        "street_address": (
                            obj.park.location.street_address
                            if hasattr(obj.park, "location") and obj.park.location
                            else ""
                        ),
                        "city": (obj.park.location.city if hasattr(obj.park, "location") and obj.park.location else ""),
                        "state": (
                            obj.park.location.state if hasattr(obj.park, "location") and obj.park.location else ""
                        ),
                        "country": (
                            obj.park.location.country if hasattr(obj.park, "location") and obj.park.location else ""
                        ),
                        "postal_code": (
                            obj.park.location.postal_code if hasattr(obj.park, "location") and obj.park.location else ""
                        ),
                        "formatted_address": (
                            obj.park.location.formatted_address
                            if hasattr(obj.park, "location") and obj.park.location
                            else ""
                        ),
                    },
                    "stats": {
                        "category": (obj.get_category_display() if obj.category else None),
                        "average_rating": (float(obj.average_rating) if obj.average_rating else None),
                        "park_name": obj.park.name,
                        "opening_date": (obj.opening_date.isoformat() if obj.opening_date else None),
                        "manufacturer": (obj.manufacturer.name if obj.manufacturer else None),
                    },
                    "nearby_locations": [],  # See FUTURE_WORK.md - THRILLWIKI-107
                }

            return Response(
                {
                    "status": "success",
                    "data": data,
                }
            )

        except Exception as e:
            capture_and_log(e, 'Get map location detail', source='api')
            return Response(
                {"status": "error", "detail": "Failed to retrieve location details"},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


@extend_schema_view(
    get=extend_schema(
        summary="Search map locations",
        description="Search locations by text query with optional bounds filtering.",
        parameters=[
            OpenApiParameter(
                "q",
                type=OpenApiTypes.STR,
                location=OpenApiParameter.QUERY,
                required=True,
                description="Search query",
            ),
            OpenApiParameter(
                "types",
                type=OpenApiTypes.STR,
                location=OpenApiParameter.QUERY,
                required=False,
                description="Comma-separated location types (park,ride)",
            ),
            OpenApiParameter(
                "page",
                type=OpenApiTypes.INT,
                location=OpenApiParameter.QUERY,
                required=False,
                description="Page number",
            ),
            OpenApiParameter(
                "page_size",
                type=OpenApiTypes.INT,
                location=OpenApiParameter.QUERY,
                required=False,
                description="Results per page",
            ),
        ],
        responses={
            200: MapSearchResponseSerializer,
            400: OpenApiTypes.OBJECT,
            500: OpenApiTypes.OBJECT,
        },
        tags=["Maps"],
    ),
)
class MapSearchAPIView(APIView):
    """
    API endpoint for searching locations by text query.

    Caching: 5-minute timeout (300s) - search results should remain consistent
    but need to reflect new content additions.
    """

    permission_classes = [AllowAny]

    @cache_api_response(timeout=300, key_prefix="map_search")
    def get(self, request: HttpRequest) -> Response:
        """Search locations by text query with pagination."""
        try:
            query = request.GET.get("q", "").strip()
            if not query:
                return Response(
                    {
                        "status": "error",
                        "detail": "Search query 'q' parameter is required",
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )

            types = request.GET.get("types", "park,ride").split(",")
            page = int(request.GET.get("page", 1))
            page_size = min(int(request.GET.get("page_size", 20)), 100)

            results = []
            total_count = 0

            # Search parks
            if "park" in types:
                parks_query = (
                    Park.objects.select_related("location")
                    .filter(
                        Q(name__icontains=query)
                        | Q(location__city__icontains=query)
                        | Q(location__state__icontains=query)
                    )
                    .filter(location__point__isnull=False)
                )

                for park in parks_query[:50]:  # Limit results
                    results.append(
                        {
                            "id": park.id,
                            "type": "park",
                            "name": park.name,
                            "slug": park.slug,
                            "latitude": (
                                park.location.latitude if hasattr(park, "location") and park.location else None
                            ),
                            "longitude": (
                                park.location.longitude if hasattr(park, "location") and park.location else None
                            ),
                            "location": {
                                "city": (park.location.city if hasattr(park, "location") and park.location else ""),
                                "state": (park.location.state if hasattr(park, "location") and park.location else ""),
                                "country": (
                                    park.location.country if hasattr(park, "location") and park.location else ""
                                ),
                            },
                            "relevance_score": 1.0,  # See FUTURE_WORK.md - THRILLWIKI-108
                        }
                    )

            # Search rides
            if "ride" in types:
                rides_query = (
                    Ride.objects.select_related("park__location")
                    .filter(
                        Q(name__icontains=query)
                        | Q(park__name__icontains=query)
                        | Q(park__location__city__icontains=query)
                    )
                    .filter(park__location__point__isnull=False)
                )

                for ride in rides_query[:50]:  # Limit results
                    results.append(
                        {
                            "id": ride.id,
                            "type": "ride",
                            "name": ride.name,
                            "slug": ride.slug,
                            "latitude": (
                                ride.park.location.latitude
                                if hasattr(ride.park, "location") and ride.park.location
                                else None
                            ),
                            "longitude": (
                                ride.park.location.longitude
                                if hasattr(ride.park, "location") and ride.park.location
                                else None
                            ),
                            "location": {
                                "city": (
                                    ride.park.location.city
                                    if hasattr(ride.park, "location") and ride.park.location
                                    else ""
                                ),
                                "state": (
                                    ride.park.location.state
                                    if hasattr(ride.park, "location") and ride.park.location
                                    else ""
                                ),
                                "country": (
                                    ride.park.location.country
                                    if hasattr(ride.park, "location") and ride.park.location
                                    else ""
                                ),
                            },
                            "relevance_score": 1.0,  # See FUTURE_WORK.md - THRILLWIKI-108
                        }
                    )

            total_count = len(results)

            # Apply pagination
            start_idx = (page - 1) * page_size
            end_idx = start_idx + page_size
            paginated_results = results[start_idx:end_idx]

            return Response(
                {
                    "status": "success",
                    "results": paginated_results,
                    "query": query,
                    "total_count": total_count,
                    "page": page,
                    "page_size": page_size,
                }
            )

        except Exception as e:
            capture_and_log(e, 'Map search', source='api')
            return Response(
                {"status": "error", "detail": "Search failed due to internal error"},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


@extend_schema_view(
    get=extend_schema(
        summary="Get locations within bounds",
        description="Get locations within specific geographic bounds.",
        parameters=[
            OpenApiParameter(
                "north",
                type=OpenApiTypes.NUMBER,
                location=OpenApiParameter.QUERY,
                required=True,
                description="Northern latitude bound",
            ),
            OpenApiParameter(
                "south",
                type=OpenApiTypes.NUMBER,
                location=OpenApiParameter.QUERY,
                required=True,
                description="Southern latitude bound",
            ),
            OpenApiParameter(
                "east",
                type=OpenApiTypes.NUMBER,
                location=OpenApiParameter.QUERY,
                required=True,
                description="Eastern longitude bound",
            ),
            OpenApiParameter(
                "west",
                type=OpenApiTypes.NUMBER,
                location=OpenApiParameter.QUERY,
                required=True,
                description="Western longitude bound",
            ),
            OpenApiParameter(
                "types",
                type=OpenApiTypes.STR,
                location=OpenApiParameter.QUERY,
                required=False,
                description="Comma-separated location types (park,ride)",
            ),
        ],
        responses={200: OpenApiTypes.OBJECT, 400: OpenApiTypes.OBJECT},
        tags=["Maps"],
    ),
)
class MapBoundsAPIView(APIView):
    """
    API endpoint for getting locations within specific bounds.

    Caching: 5-minute timeout (300s) - bounds queries are location-specific
    and may be repeated during map navigation.
    """

    permission_classes = [AllowAny]

    @cache_api_response(timeout=300, key_prefix="map_bounds")
    def get(self, request: HttpRequest) -> Response:
        """Get locations within specific geographic bounds."""
        try:
            # Parse required bounds parameters
            north_str = request.GET.get("north")
            south_str = request.GET.get("south")
            east_str = request.GET.get("east")
            west_str = request.GET.get("west")

            if not all([north_str, south_str, east_str, west_str]):
                return Response(
                    {"status": "error", "detail": "All bounds parameters (north, south, east, west) are required"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            try:
                north = float(north_str) if north_str else 0.0
                south = float(south_str) if south_str else 0.0
                east = float(east_str) if east_str else 0.0
                west = float(west_str) if west_str else 0.0
            except (TypeError, ValueError):
                return Response(
                    {"status": "error", "detail": "Invalid bounds parameters"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Validate bounds
            if north <= south:
                return Response(
                    {
                        "status": "error",
                        "detail": "North bound must be greater than south bound",
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )

            if west >= east:
                return Response(
                    {
                        "status": "error",
                        "detail": "West bound must be less than east bound",
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )

            types = request.GET.get("types", "park,ride").split(",")
            locations = []

            # Create bounds polygon
            bounds_polygon = Polygon.from_bbox((west, south, east, north))

            # Get parks within bounds
            if "park" in types:
                parks_query = Park.objects.select_related("location").filter(location__point__within=bounds_polygon)

                for park in parks_query[:100]:  # Limit results
                    locations.append(
                        {
                            "id": park.id,
                            "type": "park",
                            "name": park.name,
                            "slug": park.slug,
                            "latitude": (
                                park.location.latitude if hasattr(park, "location") and park.location else None
                            ),
                            "longitude": (
                                park.location.longitude if hasattr(park, "location") and park.location else None
                            ),
                            "status": park.status,
                        }
                    )

            # Get rides within bounds
            if "ride" in types:
                rides_query = Ride.objects.select_related("park__location").filter(
                    park__location__point__within=bounds_polygon
                )

                for ride in rides_query[:100]:  # Limit results
                    locations.append(
                        {
                            "id": ride.id,
                            "type": "ride",
                            "name": ride.name,
                            "slug": ride.slug,
                            "latitude": (
                                ride.park.location.latitude
                                if hasattr(ride.park, "location") and ride.park.location
                                else None
                            ),
                            "longitude": (
                                ride.park.location.longitude
                                if hasattr(ride.park, "location") and ride.park.location
                                else None
                            ),
                            "status": ride.status,
                        }
                    )

            return Response(
                {
                    "status": "success",
                    "locations": locations,
                    "bounds": {
                        "north": north,
                        "south": south,
                        "east": east,
                        "west": west,
                    },
                    "total_count": len(locations),
                }
            )

        except Exception as e:
            capture_and_log(e, 'Get map bounds', source='api')
            return Response(
                {"status": "error", "detail": "Failed to retrieve locations within bounds"},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


@extend_schema_view(
    get=extend_schema(
        summary="Get map service statistics",
        description="Get map service statistics and performance metrics.",
        responses={200: OpenApiTypes.OBJECT},
        tags=["Maps"],
    ),
)
class MapStatsAPIView(APIView):
    """
    API endpoint for getting map service statistics and health information.

    Caching: 10-minute timeout (600s) - stats are aggregated and change slowly.
    """

    permission_classes = [AllowAny]

    @cache_api_response(timeout=600, key_prefix="map_stats")
    def get(self, request: HttpRequest) -> Response:
        """Get map service statistics and performance metrics."""
        try:
            # Count locations with coordinates
            parks_with_location = Park.objects.filter(location__point__isnull=False).count()
            rides_with_location = Ride.objects.filter(park__location__point__isnull=False).count()
            total_locations = parks_with_location + rides_with_location

            # Get cache statistics
            from apps.core.services.enhanced_cache_service import CacheMonitor

            cache_monitor = CacheMonitor()
            cache_stats = cache_monitor.get_cache_statistics("map_locations")

            return Response(
                {
                    "status": "success",
                    "total_locations": total_locations,
                    "parks_with_location": parks_with_location,
                    "rides_with_location": rides_with_location,
                    "cache_hits": cache_stats.get("hits", 0),
                    "cache_misses": cache_stats.get("misses", 0),
                    "cache_hit_rate": cache_stats.get("hit_rate", 0.0),
                    "cache_size": cache_stats.get("size", 0),
                }
            )

        except Exception as e:
            capture_and_log(e, 'Get map stats', source='api')
            return Response(
                {"status": "error", "detail": "Failed to retrieve map statistics"},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


@extend_schema_view(
    delete=extend_schema(
        summary="Clear map cache",
        description="Clear all map cache (admin only).",
        responses={200: OpenApiTypes.OBJECT},
        tags=["Maps"],
    ),
    post=extend_schema(
        summary="Invalidate specific cache entries",
        description="Invalidate specific cache entries.",
        responses={200: OpenApiTypes.OBJECT},
        tags=["Maps"],
    ),
)
class MapCacheAPIView(APIView):
    """API endpoint for cache management (admin only)."""

    permission_classes = [IsAdminUser]  # Admin only

    def delete(self, request: HttpRequest) -> Response:
        """Clear all map cache (admin only)."""
        try:
            # Clear all map-related cache keys
            # Note: cache.keys() may not be available in all cache backends
            try:
                cache_keys = cache.keys("map_*")
                if cache_keys:
                    cache.delete_many(cache_keys)
                    cleared_count = len(cache_keys)
                else:
                    cleared_count = 0
            except AttributeError:
                # Fallback: clear cache without pattern matching
                cache.clear()
                cleared_count = 1  # Indicate cache was cleared

            return Response(
                {
                    "status": "success",
                    "detail": f"Map cache cleared successfully. Cleared {cleared_count} entries.",
                    "cleared_count": cleared_count,
                }
            )

        except Exception as e:
            capture_and_log(e, 'Clear map cache', source='api')
            return Response(
                {"status": "error", "detail": "Failed to clear map cache"},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

    def post(self, request: HttpRequest) -> Response:
        """Invalidate specific cache entries."""
        try:
            # Get cache keys to invalidate from request data
            request_data = getattr(request, "data", {})
            cache_keys = request_data.get("cache_keys", []) if request_data else []

            if cache_keys:
                cache.delete_many(cache_keys)
                invalidated_count = len(cache_keys)
            else:
                invalidated_count = 0

            return Response(
                {
                    "status": "success",
                    "detail": f"Cache invalidated successfully. Invalidated {invalidated_count} entries.",
                    "invalidated_count": invalidated_count,
                }
            )

        except Exception as e:
            capture_and_log(e, 'Invalidate map cache', source='api')
            return Response(
                {"status": "error", "detail": "Failed to invalidate cache"},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


# Legacy compatibility aliases
MapLocationsView = MapLocationsAPIView
MapLocationDetailView = MapLocationDetailAPIView
MapSearchView = MapSearchAPIView
MapBoundsView = MapBoundsAPIView
MapStatsView = MapStatsAPIView
MapCacheView = MapCacheAPIView


# =============================================================================
# Location Detection / Enrichment Endpoints
# =============================================================================


@extend_schema_view(
    post=extend_schema(
        summary="Detect user location from IP",
        description="Detect the user's approximate location based on their IP address.",
        request={
            "application/json": {
                "type": "object",
                "properties": {
                    "ip_address": {
                        "type": "string",
                        "description": "IP address to geolocate. If not provided, uses request IP.",
                    }
                },
            }
        },
        responses={
            200: {
                "type": "object",
                "properties": {
                    "latitude": {"type": "number"},
                    "longitude": {"type": "number"},
                    "city": {"type": "string"},
                    "region": {"type": "string"},
                    "country": {"type": "string"},
                    "timezone": {"type": "string"},
                },
            }
        },
        tags=["Maps"],
    ),
)
class DetectLocationView(APIView):
    """
    POST /maps/detect-location/
    Detect user's location based on IP address using a geolocation service.
    """

    permission_classes = [AllowAny]

    def post(self, request):
        try:
            # Get IP address from request or payload
            ip_address = request.data.get("ip_address")
            if not ip_address:
                # Get client IP from request
                x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
                if x_forwarded_for:
                    ip_address = x_forwarded_for.split(",")[0].strip()
                else:
                    ip_address = request.META.get("REMOTE_ADDR", "")

            # For localhost/development, return a default location
            if ip_address in ("127.0.0.1", "::1", "localhost") or ip_address.startswith("192.168."):
                return Response(
                    {
                        "latitude": 40.7128,
                        "longitude": -74.006,
                        "city": "New York",
                        "region": "New York",
                        "country": "US",
                        "country_name": "United States",
                        "timezone": "America/New_York",
                        "detected": False,
                        "reason": "localhost_fallback",
                    }
                )

            # Use IP geolocation service (ipapi.co, ipinfo.io, etc.)
            import httpx

            try:
                response = httpx.get(
                    f"https://ipapi.co/{ip_address}/json/",
                    timeout=5.0,
                    headers={"User-Agent": "ThrillWiki/1.0"},
                )
                if response.status_code == 200:
                    data = response.json()
                    return Response(
                        {
                            "latitude": data.get("latitude"),
                            "longitude": data.get("longitude"),
                            "city": data.get("city", ""),
                            "region": data.get("region", ""),
                            "country": data.get("country_code", ""),
                            "country_name": data.get("country_name", ""),
                            "timezone": data.get("timezone", ""),
                            "detected": True,
                        }
                    )
            except httpx.HTTPError as e:
                logger.warning(f"IP geolocation failed: {e}")

            # Fallback response
            return Response(
                {
                    "latitude": None,
                    "longitude": None,
                    "city": "",
                    "region": "",
                    "country": "",
                    "country_name": "",
                    "timezone": "",
                    "detected": False,
                    "reason": "geolocation_failed",
                }
            )

        except Exception as e:
            capture_and_log(e, "Detect location from IP", source="api")
            return Response(
                {"detail": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


@extend_schema_view(
    post=extend_schema(
        summary="Enrich location with geocoding",
        description="Enrich location data with reverse geocoding (coordinates to address).",
        request={
            "application/json": {
                "type": "object",
                "properties": {
                    "latitude": {"type": "number", "required": True},
                    "longitude": {"type": "number", "required": True},
                },
            }
        },
        responses={
            200: {
                "type": "object",
                "properties": {
                    "formatted_address": {"type": "string"},
                    "street_address": {"type": "string"},
                    "city": {"type": "string"},
                    "state": {"type": "string"},
                    "postal_code": {"type": "string"},
                    "country": {"type": "string"},
                },
            }
        },
        tags=["Maps"],
    ),
)
class EnrichLocationView(APIView):
    """
    POST /maps/enrich-location/
    Enrich location with reverse geocoding (coordinates to address).
    """

    permission_classes = [AllowAny]

    def post(self, request):
        try:
            latitude = request.data.get("latitude")
            longitude = request.data.get("longitude")

            if latitude is None or longitude is None:
                return Response(
                    {"detail": "latitude and longitude are required"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            try:
                lat = float(latitude)
                lng = float(longitude)
            except (TypeError, ValueError):
                return Response(
                    {"detail": "Invalid latitude or longitude"},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            # Use reverse geocoding service
            import httpx

            try:
                # Using Nominatim (OpenStreetMap) - free, no API key required
                response = httpx.get(
                    "https://nominatim.openstreetmap.org/reverse",
                    params={
                        "lat": lat,
                        "lon": lng,
                        "format": "json",
                        "addressdetails": 1,
                    },
                    timeout=5.0,
                    headers={"User-Agent": "ThrillWiki/1.0"},
                )
                if response.status_code == 200:
                    data = response.json()
                    address = data.get("address", {})
                    return Response(
                        {
                            "formatted_address": data.get("display_name", ""),
                            "street_address": address.get("road", ""),
                            "house_number": address.get("house_number", ""),
                            "city": (
                                address.get("city")
                                or address.get("town")
                                or address.get("village")
                                or ""
                            ),
                            "state": address.get("state", ""),
                            "postal_code": address.get("postcode", ""),
                            "country": address.get("country", ""),
                            "country_code": address.get("country_code", "").upper(),
                            "enriched": True,
                        }
                    )
            except httpx.HTTPError as e:
                logger.warning(f"Reverse geocoding failed: {e}")

            # Fallback response
            return Response(
                {
                    "formatted_address": "",
                    "street_address": "",
                    "city": "",
                    "state": "",
                    "postal_code": "",
                    "country": "",
                    "country_code": "",
                    "enriched": False,
                    "reason": "geocoding_failed",
                }
            )

        except Exception as e:
            capture_and_log(e, "Enrich location", source="api")
            return Response(
                {"detail": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )


@extend_schema_view(
    post=extend_schema(
        summary="Search for a location by text",
        description="Forward geocoding - convert a text query (address, city name, etc.) to coordinates.",
        request={
            "application/json": {
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "Location search query (address, city, place name, etc.)",
                    },
                    "limit": {
                        "type": "integer",
                        "description": "Maximum number of results to return (default: 5)",
                    },
                    "country": {
                        "type": "string",
                        "description": "ISO 3166-1 alpha-2 country code to restrict search",
                    },
                },
                "required": ["query"],
            }
        },
        responses={
            200: {
                "type": "object",
                "properties": {
                    "results": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "latitude": {"type": "number"},
                                "longitude": {"type": "number"},
                                "formatted_address": {"type": "string"},
                                "city": {"type": "string"},
                                "state": {"type": "string"},
                                "country": {"type": "string"},
                                "importance": {"type": "number"},
                            },
                        },
                    },
                    "query": {"type": "string"},
                    "count": {"type": "integer"},
                },
            },
            400: {"description": "Missing or invalid query parameter"},
        },
        tags=["Maps"],
    ),
)
class SearchLocationView(APIView):
    """
    POST /maps/search-location/
    Forward geocoding - search for locations by text query.
    
    Full parity with Supabase Edge Function: search-location
    
    Features:
    - Query caching with SHA-256 hash (7-day expiration)
    - Rate limiting (30 requests per minute per IP)
    - Usage logging for monitoring
    - Cache headers (X-Cache: HIT/MISS)
    """

    permission_classes = [AllowAny]
    
    # Rate limit settings matching original
    RATE_LIMIT_REQUESTS = 30
    RATE_LIMIT_PERIOD = 60  # 1 minute
    CACHE_EXPIRATION = 7 * 24 * 60 * 60  # 7 days in seconds

    def _hash_query(self, query: str) -> str:
        """Hash query for cache lookup (matching original SHA-256)."""
        import hashlib
        normalized = query.strip().lower()
        return hashlib.sha256(normalized.encode()).hexdigest()
    
    def _get_client_ip(self, request) -> str:
        """Get client IP from request headers."""
        x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
        if x_forwarded_for:
            return x_forwarded_for.split(',')[0].strip()
        return request.META.get('HTTP_X_REAL_IP') or request.META.get('REMOTE_ADDR') or 'unknown'
    
    def _check_rate_limit(self, client_ip: str) -> tuple[bool, int]:
        """
        Check if client is rate limited.
        Returns (is_allowed, current_count).
        """
        from django.core.cache import cache
        
        rate_limit_key = f"search_location:rate:{client_ip}"
        current_count = cache.get(rate_limit_key, 0)
        
        if current_count >= self.RATE_LIMIT_REQUESTS:
            return False, current_count
        
        # Increment counter with TTL
        cache.set(rate_limit_key, current_count + 1, self.RATE_LIMIT_PERIOD)
        return True, current_count + 1
    
    def _get_cached_result(self, query_hash: str):
        """Get cached result if available."""
        from django.core.cache import cache
        
        cache_key = f"search_location:query:{query_hash}"
        cached_data = cache.get(cache_key)
        
        if cached_data:
            # Update access count in a separate key
            access_key = f"search_location:access:{query_hash}"
            access_count = cache.get(access_key, 0)
            cache.set(access_key, access_count + 1, self.CACHE_EXPIRATION)
        
        return cached_data
    
    def _set_cached_result(self, query: str, query_hash: str, results: list):
        """Cache the results."""
        from django.core.cache import cache
        
        cache_key = f"search_location:query:{query_hash}"
        cache_data = {
            "query": query,
            "results": results,
            "result_count": len(results),
        }
        cache.set(cache_key, cache_data, self.CACHE_EXPIRATION)
        
        # Initialize access count
        access_key = f"search_location:access:{query_hash}"
        cache.set(access_key, 1, self.CACHE_EXPIRATION)
    
    def _log_usage(self, query: str, cache_hit: bool, api_called: bool,
                   response_time_ms: int = None, result_count: int = None,
                   client_ip: str = None, user_id: str = None,
                   error: str = None, status_code: int = None):
        """Log API usage for monitoring."""
        # Log to structured logger for now (can be enhanced to write to DB)
        logger.info(
            "OpenStreetMap API usage",
            extra={
                "query": query[:100],
                "cache_hit": cache_hit,
                "api_called": api_called,
                "response_time_ms": response_time_ms,
                "result_count": result_count,
                "client_ip": client_ip,
                "user_id": user_id,
                "error": error,
                "status_code": status_code,
            }
        )

    def post(self, request):
        import time
        import re
        start_time = time.time()
        
        client_ip = self._get_client_ip(request)
        user_id = None
        
        try:
            # Safely get user ID
            if request.user and request.user.is_authenticated:
                user_id = str(getattr(request.user, 'user_id', request.user.id))
        except Exception:
            pass
        
        try:
            # ================================================================
            # STEP 0: Sanitize and validate input
            # ================================================================
            raw_query = request.data.get("query", "")
            if not isinstance(raw_query, str):
                raw_query = str(raw_query) if raw_query else ""
            
            # Sanitize query: strip, limit length, remove control characters
            query = raw_query.strip()[:500]
            query = re.sub(r'[\x00-\x1f\x7f-\x9f]', '', query)
            
            # Validate limit
            try:
                limit = min(int(request.data.get("limit", 5)), 10)
                limit = max(limit, 1)  # At least 1
            except (ValueError, TypeError):
                limit = 5
            
            # Sanitize country code (2-letter ISO code)
            raw_country = request.data.get("country", "")
            country_code = ""
            if raw_country and isinstance(raw_country, str):
                country_code = re.sub(r'[^a-zA-Z]', '', raw_country)[:2].lower()

            
            # ================================================================
            # STEP 1: Validate query (original: min 3 characters)
            # ================================================================
            if not query:
                response_time = int((time.time() - start_time) * 1000)
                self._log_usage(
                    query="",
                    cache_hit=False,
                    api_called=False,
                    response_time_ms=response_time,
                    client_ip=client_ip,
                    user_id=user_id,
                    error="Query is required",
                    status_code=400
                )
                return Response(
                    {"error": "Query is required"},
                    status=status.HTTP_400_BAD_REQUEST,
                )
            
            if len(query) < 3:  # Match original: min 3 characters
                response_time = int((time.time() - start_time) * 1000)
                self._log_usage(
                    query=query,
                    cache_hit=False,
                    api_called=False,
                    response_time_ms=response_time,
                    client_ip=client_ip,
                    user_id=user_id,
                    error="Query must be at least 3 characters",
                    status_code=400
                )
                return Response(
                    {"error": "Query must be at least 3 characters"},
                    status=status.HTTP_400_BAD_REQUEST,
                )
            
            # ================================================================
            # STEP 2: Check rate limit (30 req/min per IP)
            # ================================================================
            is_allowed, current_count = self._check_rate_limit(client_ip)
            if not is_allowed:
                response_time = int((time.time() - start_time) * 1000)
                self._log_usage(
                    query=query,
                    cache_hit=False,
                    api_called=False,
                    response_time_ms=response_time,
                    client_ip=client_ip,
                    user_id=user_id,
                    error="Rate limit exceeded",
                    status_code=429
                )
                return Response(
                    {"error": "Rate limit exceeded. Please try again later."},
                    status=status.HTTP_429_TOO_MANY_REQUESTS,
                    headers={
                        "Retry-After": str(self.RATE_LIMIT_PERIOD),
                        "X-RateLimit-Limit": str(self.RATE_LIMIT_REQUESTS),
                        "X-RateLimit-Remaining": "0",
                    }
                )
            
            # ================================================================
            # STEP 3: Check cache
            # ================================================================
            query_hash = self._hash_query(query)
            cached = self._get_cached_result(query_hash)
            
            if cached:
                response_time = int((time.time() - start_time) * 1000)
                results = cached.get("results", [])
                
                self._log_usage(
                    query=query,
                    cache_hit=True,
                    api_called=False,
                    response_time_ms=response_time,
                    result_count=len(results),
                    client_ip=client_ip,
                    user_id=user_id,
                    status_code=200
                )
                
                # Return raw array like original (frontend handles both formats)
                response = Response(
                    results,
                    status=status.HTTP_200_OK,
                )
                response["X-Cache"] = "HIT"
                response["Cache-Control"] = "public, max-age=3600"
                return response
            
            # ================================================================
            # STEP 4: Cache miss - call Nominatim API
            # ================================================================
            import httpx
            
            try:
                params = {
                    "q": query,
                    "format": "json",
                    "addressdetails": 1,
                    "limit": limit,
                }
                if country_code:
                    params["countrycodes"] = country_code.lower()

                api_response = httpx.get(
                    "https://nominatim.openstreetmap.org/search",
                    params=params,
                    timeout=10.0,
                    headers={"User-Agent": "ThrillWiki/1.0 (https://thrillwiki.com)"},
                )

                if api_response.status_code != 200:
                    logger.warning(
                        f"Nominatim API error: {api_response.status_code}",
                        extra={"status": api_response.status_code}
                    )
                    return Response(
                        {"error": "Location search failed", "status": api_response.status_code},
                        status=api_response.status_code,
                    )

                data = api_response.json()
                response_time = int((time.time() - start_time) * 1000)
                
                # ================================================================
                # STEP 5: Cache the results (background-like, but sync in Django)
                # ================================================================
                try:
                    self._set_cached_result(query, query_hash, data)
                except Exception as cache_error:
                    logger.warning(f"Failed to cache result: {cache_error}")
                
                # Log usage
                self._log_usage(
                    query=query,
                    cache_hit=False,
                    api_called=True,
                    response_time_ms=response_time,
                    result_count=len(data) if isinstance(data, list) else 0,
                    client_ip=client_ip,
                    user_id=user_id,
                    status_code=200
                )
                
                # Return raw array like original Nominatim response
                response = Response(
                    data,
                    status=status.HTTP_200_OK,
                )
                response["X-Cache"] = "MISS"
                response["Cache-Control"] = "public, max-age=3600"
                return response

            except httpx.HTTPError as e:
                logger.warning(f"Forward geocoding failed: {e}")
                response_time = int((time.time() - start_time) * 1000)
                
                self._log_usage(
                    query=query,
                    cache_hit=False,
                    api_called=True,
                    response_time_ms=response_time,
                    client_ip=client_ip,
                    user_id=user_id,
                    error=str(e),
                    status_code=500
                )
                
                return Response(
                    {"error": "Failed to fetch location data"},
                    status=status.HTTP_500_INTERNAL_SERVER_ERROR,
                )

        except ValueError as e:
            return Response(
                {"error": f"Invalid parameter: {str(e)}"},
                status=status.HTTP_400_BAD_REQUEST,
            )
        except Exception as e:
            capture_and_log(e, "Search location", source="api")
            return Response(
                {"error": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )
