"""
API views for the unified map service.
Enhanced with proper error handling, pagination, and performance optimizations.
"""

import json
import logging
import time
from typing import Any

from django.conf import settings
from django.core.exceptions import ValidationError
from django.http import HttpRequest, JsonResponse
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.cache import cache_page
from django.views.decorators.gzip import gzip_page

from ..services.data_structures import GeoBounds, LocationType, MapFilters
from ..services.map_service import unified_map_service
from apps.core.utils import capture_and_log

logger = logging.getLogger(__name__)


class MapAPIView(View):
    """Base view for map API endpoints with common functionality."""

    # Pagination settings
    DEFAULT_PAGE_SIZE = 50
    MAX_PAGE_SIZE = 200

    def dispatch(self, request, *args, **kwargs):
        """Add CORS headers, compression, and handle preflight requests."""
        start_time = time.time()

        try:
            response = super().dispatch(request, *args, **kwargs)

            # Add CORS headers for API access
            response["Access-Control-Allow-Origin"] = "*"
            response["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"
            response["Access-Control-Allow-Headers"] = "Content-Type, Authorization"

            # Add performance headers
            response["X-Response-Time"] = f"{(time.time() - start_time) * 1000:.2f}ms"

            # Add compression hint for large responses
            if hasattr(response, "content") and len(response.content) > 1024:
                response["Content-Encoding"] = "gzip"

            return response

        except Exception as e:
            capture_and_log(e, f'Map API dispatch {request.path}', source='api')
            return self._error_response("An internal server error occurred", status=500)

    def options(self, request, *args, **kwargs):
        """Handle preflight CORS requests."""
        return JsonResponse({}, status=200)

    def _parse_bounds(self, request: HttpRequest) -> GeoBounds | None:
        """Parse geographic bounds from request parameters."""
        try:
            north = request.GET.get("north")
            south = request.GET.get("south")
            east = request.GET.get("east")
            west = request.GET.get("west")

            if all(param is not None for param in [north, south, east, west]):
                bounds = GeoBounds(
                    north=float(north),
                    south=float(south),
                    east=float(east),
                    west=float(west),
                )

                # Validate bounds
                if not (-90 <= bounds.south <= bounds.north <= 90):
                    raise ValidationError("Invalid latitude bounds")
                if not (-180 <= bounds.west <= bounds.east <= 180):
                    raise ValidationError("Invalid longitude bounds")

                return bounds
            return None
        except (ValueError, TypeError) as e:
            raise ValidationError(f"Invalid bounds parameters: {e}") from None

    def _parse_pagination(self, request: HttpRequest) -> dict[str, int]:
        """Parse pagination parameters from request."""
        try:
            page = max(1, int(request.GET.get("page", 1)))
            page_size = min(
                self.MAX_PAGE_SIZE,
                max(
                    1,
                    int(request.GET.get("page_size", self.DEFAULT_PAGE_SIZE)),
                ),
            )
            offset = (page - 1) * page_size

            return {
                "page": page,
                "page_size": page_size,
                "offset": offset,
                "limit": page_size,
            }
        except (ValueError, TypeError):
            return {
                "page": 1,
                "page_size": self.DEFAULT_PAGE_SIZE,
                "offset": 0,
                "limit": self.DEFAULT_PAGE_SIZE,
            }

    def _parse_filters(self, request: HttpRequest) -> MapFilters | None:
        """Parse filtering parameters from request."""
        try:
            filters = MapFilters()

            # Location types
            location_types_param = request.GET.get("types")
            if location_types_param:
                type_strings = location_types_param.split(",")
                valid_types = {lt.value for lt in LocationType}
                filters.location_types = {LocationType(t.strip()) for t in type_strings if t.strip() in valid_types}

            # Park status
            park_status_param = request.GET.get("park_status")
            if park_status_param:
                filters.park_status = set(park_status_param.split(","))

            # Ride types
            ride_types_param = request.GET.get("ride_types")
            if ride_types_param:
                filters.ride_types = set(ride_types_param.split(","))

            # Company roles
            company_roles_param = request.GET.get("company_roles")
            if company_roles_param:
                filters.company_roles = set(company_roles_param.split(","))

            # Search query with length validation
            search_query = request.GET.get("q") or request.GET.get("search")
            if search_query and len(search_query.strip()) >= 2:
                filters.search_query = search_query.strip()

            # Rating filter with validation
            min_rating_param = request.GET.get("min_rating")
            if min_rating_param:
                min_rating = float(min_rating_param)
                if 0 <= min_rating <= 10:
                    filters.min_rating = min_rating

            # Geographic filters with validation
            country = request.GET.get("country", "").strip()
            if country and len(country) >= 2:
                filters.country = country

            state = request.GET.get("state", "").strip()
            if state and len(state) >= 2:
                filters.state = state

            city = request.GET.get("city", "").strip()
            if city and len(city) >= 2:
                filters.city = city

            # Coordinates requirement
            has_coordinates_param = request.GET.get("has_coordinates")
            if has_coordinates_param is not None:
                filters.has_coordinates = has_coordinates_param.lower() in [
                    "true",
                    "1",
                    "yes",
                ]

            return (
                filters
                if any(
                    [
                        filters.location_types,
                        filters.park_status,
                        filters.ride_types,
                        filters.company_roles,
                        filters.search_query,
                        filters.min_rating,
                        filters.country,
                        filters.state,
                        filters.city,
                    ]
                )
                else None
            )

        except (ValueError, TypeError) as e:
            raise ValidationError(f"Invalid filter parameters: {e}") from None

    def _parse_zoom_level(self, request: HttpRequest) -> int:
        """Parse zoom level from request with default."""
        try:
            zoom_param = request.GET.get("zoom", "10")
            zoom_level = int(zoom_param)
            return max(1, min(20, zoom_level))  # Clamp between 1 and 20
        except (ValueError, TypeError):
            return 10  # Default zoom level

    def _create_paginated_response(
        self,
        data: list,
        total_count: int,
        pagination: dict[str, int],
        request: HttpRequest,
    ) -> dict[str, Any]:
        """Create paginated response with metadata."""
        total_pages = (total_count + pagination["page_size"] - 1) // pagination["page_size"]

        # Build pagination URLs
        base_url = request.build_absolute_uri(request.path)
        query_params = request.GET.copy()

        next_url = None
        if pagination["page"] < total_pages:
            query_params["page"] = pagination["page"] + 1
            next_url = f"{base_url}?{query_params.urlencode()}"

        prev_url = None
        if pagination["page"] > 1:
            query_params["page"] = pagination["page"] - 1
            prev_url = f"{base_url}?{query_params.urlencode()}"

        return {
            "status": "success",
            "data": data,
            "pagination": {
                "page": pagination["page"],
                "page_size": pagination["page_size"],
                "total_pages": total_pages,
                "total_count": total_count,
                "has_next": pagination["page"] < total_pages,
                "has_previous": pagination["page"] > 1,
                "next_url": next_url,
                "previous_url": prev_url,
            },
        }

    def _error_response(
        self,
        message: str,
        status: int = 400,
        error_code: str = None,
        details: dict[str, Any] = None,
    ) -> JsonResponse:
        """Return standardized error response with enhanced information."""
        response_data = {
            "status": "error",
            "message": message,
            "timestamp": time.time(),
            "data": None,
        }

        if error_code:
            response_data["error_code"] = error_code

        if details:
            response_data["details"] = details

        # Add request ID for debugging in production
        if hasattr(settings, "DEBUG") and not settings.DEBUG:
            response_data["request_id"] = getattr(self.request, "id", None)

        return JsonResponse(response_data, status=status)

    def _success_response(self, data: Any, message: str = None, metadata: dict[str, Any] = None) -> JsonResponse:
        """Return standardized success response."""
        response_data = {
            "status": "success",
            "data": data,
            "timestamp": time.time(),
        }

        if message:
            response_data["message"] = message

        if metadata:
            response_data["metadata"] = metadata

        return JsonResponse(response_data)


class MapLocationsView(MapAPIView):
    """
    API endpoint for getting map locations with optional clustering.

    GET /api/map/locations/
    Parameters:
        - north, south, east, west: Bounding box coordinates
        - zoom: Zoom level (1-20)
        - types: Comma-separated location types (park,ride,company,generic)
        - cluster: Whether to enable clustering (true/false)
        - q: Search query
        - park_status: Park status filter
        - ride_types: Ride type filter
        - min_rating: Minimum rating filter
        - country, state, city: Geographic filters
    """

    @method_decorator(cache_page(300))  # Cache for 5 minutes
    @method_decorator(gzip_page)  # Compress large responses
    def get(self, request: HttpRequest) -> JsonResponse:
        """Get map locations with optional clustering and filtering."""
        try:
            # Parse parameters
            bounds = self._parse_bounds(request)
            filters = self._parse_filters(request)
            zoom_level = self._parse_zoom_level(request)
            pagination = self._parse_pagination(request)

            # Clustering preference
            cluster_param = request.GET.get("cluster", "true")
            enable_clustering = cluster_param.lower() in ["true", "1", "yes"]

            # Cache preference
            use_cache_param = request.GET.get("cache", "true")
            use_cache = use_cache_param.lower() in ["true", "1", "yes"]

            # Validate request
            if not enable_clustering and not bounds and not filters:
                return self._error_response(
                    "Either bounds, filters, or clustering must be specified for non-clustered requests",
                    error_code="MISSING_PARAMETERS",
                )

            # Get map data
            response = unified_map_service.get_map_data(
                bounds=bounds,
                filters=filters,
                zoom_level=zoom_level,
                cluster=enable_clustering,
                use_cache=use_cache,
            )

            # Handle pagination for non-clustered results
            if not enable_clustering and response.locations:
                start_idx = pagination["offset"]
                end_idx = start_idx + pagination["limit"]
                paginated_locations = response.locations[start_idx:end_idx]

                return JsonResponse(
                    self._create_paginated_response(
                        [loc.to_dict() for loc in paginated_locations],
                        len(response.locations),
                        pagination,
                        request,
                    )
                )

            # For clustered results, return as-is with metadata
            response_dict = response.to_dict()

            return self._success_response(
                response_dict,
                metadata={
                    "clustered": response.clustered,
                    "cache_hit": response.cache_hit,
                    "query_time_ms": response.query_time_ms,
                    "filters_applied": response.filters_applied,
                },
            )

        except ValidationError as e:
            logger.warning(f"Validation error in MapLocationsView: {str(e)}")
            return self._error_response(str(e), 400, error_code="VALIDATION_ERROR")
        except Exception as e:
            capture_and_log(e, 'MapLocationsView get', source='api')
            return self._error_response(
                "Failed to retrieve map locations",
                500,
                error_code="INTERNAL_ERROR",
            )


class MapLocationDetailView(MapAPIView):
    """
    API endpoint for getting detailed information about a specific location.

    GET /api/map/locations/<type>/<id>/
    """

    @method_decorator(cache_page(600))  # Cache for 10 minutes
    def get(self, request: HttpRequest, location_type: str, location_id: int) -> JsonResponse:
        """Get detailed information for a specific location."""
        try:
            # Validate location type
            valid_types = [lt.value for lt in LocationType]
            if location_type not in valid_types:
                return self._error_response(
                    f"Invalid location type: {location_type}. Valid types: {
                        ', '.join(valid_types)
                    }",
                    400,
                    error_code="INVALID_LOCATION_TYPE",
                )

            # Validate location ID
            if location_id <= 0:
                return self._error_response(
                    "Location ID must be a positive integer",
                    400,
                    error_code="INVALID_LOCATION_ID",
                )

            # Get location details
            location = unified_map_service.get_location_details(location_type, location_id)

            if not location:
                return self._error_response(
                    f"Location not found: {location_type}/{location_id}",
                    404,
                    error_code="LOCATION_NOT_FOUND",
                )

            return self._success_response(
                location.to_dict(),
                metadata={
                    "location_type": location_type,
                    "location_id": location_id,
                },
            )

        except ValueError as e:
            logger.warning(f"Value error in MapLocationDetailView: {str(e)}")
            return self._error_response(str(e), 400, error_code="INVALID_PARAMETER")
        except Exception as e:
            capture_and_log(e, 'MapLocationDetailView get', source='api')
            return self._error_response(
                "Failed to retrieve location details",
                500,
                error_code="INTERNAL_ERROR",
            )


class MapSearchView(MapAPIView):
    """
    API endpoint for searching locations by text query.

    GET /api/map/search/
    Parameters:
        - q: Search query (required)
        - north, south, east, west: Optional bounding box
        - types: Comma-separated location types
        - limit: Maximum results (default 50)
    """

    @method_decorator(gzip_page)  # Compress responses
    def get(self, request: HttpRequest) -> JsonResponse:
        """Search locations by text query with pagination."""
        try:
            # Get and validate search query
            query = request.GET.get("q", "").strip()
            if not query:
                return self._error_response(
                    "Search query 'q' parameter is required",
                    400,
                    error_code="MISSING_QUERY",
                )

            if len(query) < 2:
                return self._error_response(
                    "Search query must be at least 2 characters long",
                    400,
                    error_code="QUERY_TOO_SHORT",
                )

            # Parse parameters
            bounds = self._parse_bounds(request)
            pagination = self._parse_pagination(request)

            # Parse location types
            location_types = None
            types_param = request.GET.get("types")
            if types_param:
                try:
                    valid_types = {lt.value for lt in LocationType}
                    location_types = {
                        LocationType(t.strip()) for t in types_param.split(",") if t.strip() in valid_types
                    }
                except ValueError:
                    return self._error_response(
                        "Invalid location types",
                        400,
                        error_code="INVALID_TYPES",
                    )

            # Set reasonable search limit (higher for search than general
            # listings)
            search_limit = min(500, pagination["page"] * pagination["page_size"])

            # Perform search
            locations = unified_map_service.search_locations(
                query=query,
                bounds=bounds,
                location_types=location_types,
                limit=search_limit,
            )

            # Apply pagination
            start_idx = pagination["offset"]
            end_idx = start_idx + pagination["limit"]
            paginated_locations = locations[start_idx:end_idx]

            return JsonResponse(
                self._create_paginated_response(
                    [loc.to_dict() for loc in paginated_locations],
                    len(locations),
                    pagination,
                    request,
                )
            )

        except ValidationError as e:
            logger.warning(f"Validation error in MapSearchView: {str(e)}")
            return self._error_response(str(e), 400, error_code="VALIDATION_ERROR")
        except ValueError as e:
            logger.warning(f"Value error in MapSearchView: {str(e)}")
            return self._error_response(str(e), 400, error_code="INVALID_PARAMETER")
        except Exception as e:
            capture_and_log(e, 'MapSearchView get', source='api')
            return self._error_response(
                "Search failed due to internal error",
                500,
                error_code="SEARCH_FAILED",
            )


class MapBoundsView(MapAPIView):
    """
    API endpoint for getting locations within specific bounds.

    GET /api/map/bounds/
    Parameters:
        - north, south, east, west: Bounding box coordinates (required)
        - types: Comma-separated location types
        - zoom: Zoom level
    """

    @method_decorator(cache_page(300))  # Cache for 5 minutes
    def get(self, request: HttpRequest) -> JsonResponse:
        """Get locations within specific geographic bounds."""
        try:
            # Parse required bounds
            bounds = self._parse_bounds(request)
            if not bounds:
                return self._error_response("Bounds parameters required: north, south, east, west", 400)

            # Parse optional filters
            location_types = None
            types_param = request.GET.get("types")
            if types_param:
                location_types = {
                    LocationType(t.strip())
                    for t in types_param.split(",")
                    if t.strip() in [lt.value for lt in LocationType]
                }

            zoom_level = self._parse_zoom_level(request)

            # Get locations within bounds
            response = unified_map_service.get_locations_by_bounds(
                north=bounds.north,
                south=bounds.south,
                east=bounds.east,
                west=bounds.west,
                location_types=location_types,
                zoom_level=zoom_level,
            )

            return JsonResponse(response.to_dict())

        except ValidationError as e:
            return self._error_response(str(e), 400)
        except Exception as e:
            return self._error_response(
                f"Internal server error: {str(e)}",
                500,
            )


class MapStatsView(MapAPIView):
    """
    API endpoint for getting map service statistics and health information.

    GET /api/map/stats/
    """

    def get(self, request: HttpRequest) -> JsonResponse:
        """Get map service statistics and performance metrics."""
        try:
            stats = unified_map_service.get_service_stats()

            return JsonResponse({"status": "success", "data": stats})

        except Exception as e:
            return self._error_response(
                f"Internal server error: {str(e)}",
                500,
            )


class MapCacheView(MapAPIView):
    """
    API endpoint for cache management (admin only).

    DELETE /api/map/cache/
    POST /api/map/cache/invalidate/
    """

    def delete(self, request: HttpRequest) -> JsonResponse:
        """Clear all map cache (admin only)."""
        if not (request.user.is_authenticated and request.user.is_staff):
            return self._error_response("Admin access required", 403)
        try:
            unified_map_service.invalidate_cache()

            return JsonResponse(
                {
                    "status": "success",
                    "message": "Map cache cleared successfully",
                }
            )

        except Exception as e:
            return self._error_response(
                f"Internal server error: {str(e)}",
                500,
            )

    def post(self, request: HttpRequest) -> JsonResponse:
        """Invalidate specific cache entries."""
        if not (request.user.is_authenticated and request.user.is_staff):
            return self._error_response("Admin access required", 403)
        try:
            data = json.loads(request.body)

            location_type = data.get("location_type")
            location_id = data.get("location_id")
            bounds_data = data.get("bounds")

            bounds = None
            if bounds_data:
                bounds = GeoBounds(**bounds_data)

            unified_map_service.invalidate_cache(
                location_type=location_type,
                location_id=location_id,
                bounds=bounds,
            )

            return JsonResponse(
                {
                    "status": "success",
                    "message": "Cache invalidated successfully",
                }
            )

        except (json.JSONDecodeError, TypeError, ValueError) as e:
            return self._error_response(f"Invalid request data: {str(e)}", 400)
        except Exception as e:
            return self._error_response(
                f"Internal server error: {str(e)}",
                500,
            )
