"""
Road trip planning views for theme parks.
Provides interfaces for creating and managing multi-park road trips.
"""

import json
from typing import Any

from django.http import HttpRequest, HttpResponse, JsonResponse
from django.shortcuts import render
from django.urls import reverse
from django.views.generic import TemplateView, View

from apps.core.services.data_structures import LocationType
from apps.core.services.map_service import unified_map_service

from .models import Park
from .services.roadtrip import RoadTripService

JSON_DECODE_ERROR_MSG = "Invalid JSON data"
PARKS_ALONG_ROUTE_HTML = "parks/partials/parks_along_route.html"


class RoadTripViewMixin:
    """Mixin providing common functionality for road trip views."""

    def __init__(self):
        super().__init__()
        self.roadtrip_service = RoadTripService()

    def get_roadtrip_context(self) -> dict[str, Any]:
        """Get common context data for road trip views."""
        return {
            "roadtrip_api_urls": {
                "create_trip": "/roadtrip/create/",
                "find_parks_along_route": "/roadtrip/htmx/parks-along-route/",
                "geocode": "/roadtrip/htmx/geocode/",
            },
            "max_parks_per_trip": 10,
            "default_detour_km": 50,
            "enable_osm_integration": True,
        }


class RoadTripPlannerView(RoadTripViewMixin, TemplateView):
    """
    Main road trip planning interface.

    URL: /roadtrip/
    """

    template_name = "parks/roadtrip_planner.html"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context.update(self.get_roadtrip_context(self.request))

        # Get popular parks for suggestions
        popular_parks = (
            Park.objects.filter(status="OPERATING", location__isnull=False)
            .select_related("location", "operator")
            .order_by("-ride_count")[:20]
        )

        context.update(
            {
                "page_title": "Road Trip Planner",
                "popular_parks": popular_parks,
                "countries_with_parks": self._get_countries_with_parks(),
                "enable_route_optimization": True,
                "show_distance_estimates": True,
            }
        )

        return context

    def _get_countries_with_parks(self) -> list[str]:
        """Get list of countries that have theme parks."""
        countries = (
            Park.objects.filter(status="OPERATING", location__country__isnull=False)
            .values_list("location__country", flat=True)
            .distinct()
            .order_by("location__country")
        )
        return list(countries)


class CreateTripView(RoadTripViewMixin, View):
    """
    Generate optimized road trip routes.

    URL: /roadtrip/create/
    """

    def post(self, request: HttpRequest) -> HttpResponse:
        """Create a new road trip with optimized routing."""
        try:
            data = json.loads(request.body)

            # Parse park IDs
            park_ids = data.get("park_ids", [])
            if not park_ids or len(park_ids) < 2:
                return JsonResponse(
                    {
                        "status": "error",
                        "message": "At least 2 parks are required for a road trip",
                    },
                    status=400,
                )

            if len(park_ids) > 10:
                return JsonResponse(
                    {
                        "status": "error",
                        "message": "Maximum 10 parks allowed per trip",
                    },
                    status=400,
                )

            # Get parks
            parks = list(
                Park.objects.filter(id__in=park_ids, location__isnull=False).select_related("location", "operator")
            )

            if len(parks) != len(park_ids):
                return JsonResponse(
                    {
                        "status": "error",
                        "message": "Some parks could not be found or do not have location data",
                    },
                    status=400,
                )

            # Create optimized trip
            trip = self.roadtrip_service.create_multi_park_trip(parks)

            if not trip:
                return JsonResponse(
                    {
                        "status": "error",
                        "message": "Could not create optimized route for the selected parks",
                    },
                    status=400,
                )

            # Convert trip to dict for JSON response
            trip_data = {
                "parks": [self._park_to_dict(park) for park in trip.parks],
                "legs": [self._leg_to_dict(leg) for leg in trip.legs],
                "total_distance_km": trip.total_distance_km,
                "total_duration_minutes": trip.total_duration_minutes,
                "formatted_total_distance": trip.formatted_total_distance,
                "formatted_total_duration": trip.formatted_total_duration,
            }

            return JsonResponse(
                {
                    "status": "success",
                    "data": trip_data,
                    "trip_url": reverse("parks:roadtrip_detail", kwargs={"trip_id": "temp"}),
                }
            )

        except json.JSONDecodeError:
            return JsonResponse(
                {"status": "error", "message": JSON_DECODE_ERROR_MSG},
                status=400,
            )
        except Exception as e:
            return JsonResponse(
                {
                    "status": "error",
                    "message": f"Failed to create trip: {str(e)}",
                },
                status=500,
            )

    def _park_to_dict(self, park: Park) -> dict[str, Any]:
        """Convert park instance to dictionary."""
        return {
            "id": park.id,
            "name": park.name,
            "slug": park.slug,
            "formatted_location": getattr(park, "formatted_location", ""),
            "coordinates": park.coordinates,
            "operator": park.operator.name if park.operator else None,
            "ride_count": getattr(park, "ride_count", 0),
            "url": reverse("parks:park_detail", kwargs={"slug": park.slug}),
        }

    def _leg_to_dict(self, leg) -> dict[str, Any]:
        """Convert trip leg to dictionary."""
        return {
            "from_park": self._park_to_dict(leg.from_park),
            "to_park": self._park_to_dict(leg.to_park),
            "distance_km": leg.route.distance_km,
            "duration_minutes": leg.route.duration_minutes,
            "formatted_distance": leg.route.formatted_distance,
            "formatted_duration": leg.route.formatted_duration,
            "geometry": leg.route.geometry,
        }


class TripDetailView(RoadTripViewMixin, TemplateView):
    """
    Show trip details and map.

    URL: /roadtrip/<trip_id>/
    """

    template_name = "parks/trip_detail.html"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context.update(self.get_roadtrip_context(self.request))

        # For now, this is a placeholder since we don't persist trips
        # In a full implementation, you would retrieve the trip from database
        trip_id = kwargs.get("trip_id")

        context.update(
            {
                "page_title": f"Road Trip #{trip_id}",
                "trip_id": trip_id,
                "message": "Trip details would be loaded here. Currently trips are not persisted.",
            }
        )

        return context


class FindParksAlongRouteView(RoadTripViewMixin, View):
    """
    HTMX endpoint for route-based park discovery.

    URL: /roadtrip/htmx/parks-along-route/
    """

    def post(self, request: HttpRequest) -> HttpResponse:
        """Find parks along a route between two points."""
        try:
            data = json.loads(request.body)

            start_park_id = data.get("start_park_id")
            end_park_id = data.get("end_park_id")
            max_detour_km = min(100, max(10, float(data.get("max_detour_km", 50))))

            if not start_park_id or not end_park_id:
                return render(
                    request,
                    PARKS_ALONG_ROUTE_HTML,
                    {"error": "Start and end parks are required"},
                )

            # Get start and end parks
            try:
                start_park = Park.objects.select_related("location").get(id=start_park_id, location__isnull=False)
                end_park = Park.objects.select_related("location").get(id=end_park_id, location__isnull=False)
            except Park.DoesNotExist:
                return render(
                    request,
                    PARKS_ALONG_ROUTE_HTML,
                    {"error": "One or both parks could not be found"},
                )

            # Find parks along route
            parks_along_route = self.roadtrip_service.find_parks_along_route(start_park, end_park, max_detour_km)

            # Return JSON if requested
            if request.headers.get("Accept") == "application/json" or request.content_type == "application/json":
                return JsonResponse(
                    {
                        "status": "success",
                        "data": {
                            "parks": [self._park_to_dict(p) for p in parks_along_route],
                            "start_park": self._park_to_dict(start_park),
                            "end_park": self._park_to_dict(end_park),
                            "count": len(parks_along_route),
                        },
                    }
                )

            return render(
                request,
                PARKS_ALONG_ROUTE_HTML,
                {
                    "parks": parks_along_route,
                    "start_park": start_park,
                    "end_park": end_park,
                    "max_detour_km": max_detour_km,
                    "count": len(parks_along_route),
                },
            )

        except json.JSONDecodeError:
            return render(
                request,
                PARKS_ALONG_ROUTE_HTML,
                {"error": JSON_DECODE_ERROR_MSG},
            )
        except Exception as e:
            return render(request, PARKS_ALONG_ROUTE_HTML, {"error": str(e)})


class GeocodeAddressView(RoadTripViewMixin, View):
    """
    HTMX endpoint for geocoding addresses.

    URL: /roadtrip/htmx/geocode/
    """

    def post(self, request: HttpRequest) -> HttpResponse:
        """Geocode an address and find nearby parks."""
        try:
            data = json.loads(request.body)
            address = data.get("address", "").strip()

            if not address:
                return JsonResponse(
                    {"status": "error", "message": "Address is required"},
                    status=400,
                )

            # Geocode the address
            coordinates = self.roadtrip_service.geocode_address(address)

            if not coordinates:
                return JsonResponse(
                    {
                        "status": "error",
                        "message": "Could not geocode the provided address",
                    },
                    status=400,
                )

            # Find nearby parks
            radius_km = min(200, max(10, float(data.get("radius_km", 100))))

            # Use map service to find parks near coordinates
            from core.services.data_structures import GeoBounds

            # Create a bounding box around the coordinates
            lat_delta = radius_km / 111.0  # Rough conversion: 1 degree ≈ 111km
            lng_delta = radius_km / (111.0 * abs(coordinates.latitude / 90.0))

            bounds = GeoBounds(
                north=coordinates.latitude + lat_delta,
                south=coordinates.latitude - lat_delta,
                east=coordinates.longitude + lng_delta,
                west=coordinates.longitude - lng_delta,
            )

            map_response = unified_map_service.get_locations_by_bounds(
                north=bounds.north,
                south=bounds.south,
                east=bounds.east,
                west=bounds.west,
                location_types={LocationType.PARK},
            )

            return JsonResponse(
                {
                    "status": "success",
                    "data": {
                        "coordinates": {
                            "latitude": coordinates.latitude,
                            "longitude": coordinates.longitude,
                        },
                        "address": address,
                        "nearby_parks": [loc.to_dict() for loc in map_response.locations[:20]],
                        "radius_km": radius_km,
                    },
                }
            )

        except json.JSONDecodeError:
            return JsonResponse(
                {"status": "error", "message": JSON_DECODE_ERROR_MSG},
                status=400,
            )
        except Exception as e:
            return JsonResponse({"status": "error", "message": str(e)}, status=500)


class ParkDistanceCalculatorView(RoadTripViewMixin, View):
    """
    HTMX endpoint for calculating distances between parks.

    URL: /roadtrip/htmx/distance/
    """

    def post(self, request: HttpRequest) -> HttpResponse:
        """Calculate distance and duration between two parks."""
        try:
            data = json.loads(request.body)

            park1_id = data.get("park1_id")
            park2_id = data.get("park2_id")

            if not park1_id or not park2_id:
                return JsonResponse(
                    {
                        "status": "error",
                        "message": "Both park IDs are required",
                    },
                    status=400,
                )

            # Get parks
            try:
                park1 = Park.objects.select_related("location").get(id=park1_id, location__isnull=False)
                park2 = Park.objects.select_related("location").get(id=park2_id, location__isnull=False)
            except Park.DoesNotExist:
                return JsonResponse(
                    {
                        "status": "error",
                        "message": "One or both parks could not be found",
                    },
                    status=400,
                )

            # Calculate route
            coords1 = park1.coordinates
            coords2 = park2.coordinates

            if not coords1 or not coords2:
                return JsonResponse(
                    {
                        "status": "error",
                        "message": "One or both parks do not have coordinate data",
                    },
                    status=400,
                )

            from services.roadtrip import Coordinates

            route = self.roadtrip_service.calculate_route(Coordinates(*coords1), Coordinates(*coords2))

            if not route:
                return JsonResponse(
                    {
                        "status": "error",
                        "message": "Could not calculate route between parks",
                    },
                    status=400,
                )

            return JsonResponse(
                {
                    "status": "success",
                    "data": {
                        "distance_km": route.distance_km,
                        "duration_minutes": route.duration_minutes,
                        "formatted_distance": route.formatted_distance,
                        "formatted_duration": route.formatted_duration,
                        "park1": {
                            "name": park1.name,
                            "formatted_location": getattr(park1, "formatted_location", ""),
                        },
                        "park2": {
                            "name": park2.name,
                            "formatted_location": getattr(park2, "formatted_location", ""),
                        },
                    },
                }
            )

        except json.JSONDecodeError:
            return JsonResponse(
                {"status": "error", "message": JSON_DECODE_ERROR_MSG},
                status=400,
            )
        except Exception as e:
            return JsonResponse({"status": "error", "message": str(e)}, status=500)
