import googlemaps
import requests
from django.conf import settings
from geopy.distance import geodesic
from geopy.geocoders import GoogleV3
from rest_framework import status
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework.views import APIView


class NearbyPlacesAPIView(APIView):
    """
    API view for retrieving nearby places based on location and optional place type.

    Attributes:
        permission_classes (tuple): Specifies the permissions required to access this view.
        http_method_names (list): A list of HTTP methods that this view supports.

    Methods:
        get(request, *args, **kwargs): Handles GET requests to retrieve nearby places.
    """

    permission_classes = (AllowAny,)
    http_method_names = ["get"]

    def get(self, request, *args, **kwargs):
        """
        Handles GET requests to retrieve nearby places.

        Args:
            request (HttpRequest): The HTTP request object.
            *args: Variable-length argument list.
            **kwargs: Arbitrary keyword arguments.

        Returns:
            Response: A response containing a list of nearby places.

        Raises:
            HttpResponseBadRequest: If the required 'location' parameter is not provided.
        """

        location = request.GET.get("location")

        # Check if 'location' parameter is provided
        if not location:
            return Response(
                {"message": "Location is required"}, status=status.HTTP_400_BAD_REQUEST
            )

        # Get API key from settings
        api_key = settings.GOOGLE_MAP_API_KEY

        # Extract latitude and longitude from the 'location' parameter
        latitude, longitude = map(float, location.split(","))

        # Extract optional 'place_type' and 'next_page_token' parameters
        place_type = request.GET.get("place_type")
        next_page_token = request.GET.get("next_page_token")

        try:
            gmaps = googlemaps.Client(key=api_key)

            # Define the parameters for nearby search
            params = {
                "location": f"{latitude},{longitude}",
                "radius": settings.NEAR_BY_PLACE_RADIUS,
            }

            # Add optional parameters if specified
            if place_type:
                params["type"] = place_type

            if next_page_token:
                params["page_token"] = next_page_token

            # Make the API request for nearby places
            places_result = gmaps.places_nearby(**params)

            # Extract relevant information from the results
            places = []
            for place in places_result.get("results", []):
                # Fetch place details, including photos
                place_details = gmaps.place(
                    place["place_id"],
                    fields=[
                        "name",
                        "formatted_address",
                        "formatted_phone_number",
                        "geometry",
                        "photo",
                    ],
                )
                place_details = place_details["result"]

                # Extract photo URLs from place details
                photos = []
                for photo_info in place_details.get("photos", []):
                    photo_reference = photo_info.get("photo_reference")
                    if photo_reference:
                        # Construct a URL to fetch the photo
                        photo_url = (
                            f"https://maps.googleapis.com/maps/api/place/photo?maxwidth=400&photoreference="
                            f"{photo_reference}&key={api_key}"
                        )
                        photos.append(photo_url)

                # Calculate distance from the specified location in kilometers
                distance = geodesic(
                    (latitude, longitude),
                    (
                        place_details["geometry"]["location"]["lat"],
                        place_details["geometry"]["location"]["lng"],
                    ),
                ).kilometers

                places.append(
                    {
                        "name": place_details.get("name"),
                        "address": place_details.get("formatted_address", ""),
                        "phone_number": place_details.get("formatted_phone_number"),
                        "latitude": place_details["geometry"]["location"]["lat"],
                        "longitude": place_details["geometry"]["location"]["lng"],
                        "photos": photos,
                        "distance": round(distance, 2),
                    }
                )

            # Extract next_page_token for paginated results
            next_page_token = places_result.get("next_page_token")

            # Construct the response with places and optional next_page_token
            response = {"results": places, "next": next_page_token}

            return Response(response, status=status.HTTP_200_OK)

        except googlemaps.exceptions.ApiError as err:
            return Response([], status=status.HTTP_200_OK)


class GetAddressFromLatLngAPIView(APIView):
    """
    API view to retrieve address information from latitude and longitude.

    This API endpoint expects GET requests with latitude and longitude query parameters.
    - Required parameters:
        - latitude: Decimal latitude value.
        - longitude: Decimal longitude value.

    Returns a JSON response with the address information found for the given coordinates,
    or an appropriate error message if the request is invalid or the address cannot be found.
    """

    permission_classes = (AllowAny,)
    http_method_names = ["get"]

    def get(self, request, *args, **kwargs):
        """
        Handle GET requests to retrieve address information.

        - Retrieves latitude and longitude values from query parameters.
        - Validates that both latitude and longitude are provided.
        - Retrieves API key from settings (assuming `GOOGLE_MAP_API_KEY` is defined).
        - Creates a GoogleV3 geolocator using the API key.
        - Uses the geolocator to perform reverse geocoding on the provided coordinates.
        - Returns a JSON response with the address information if found, otherwise an error message.
        - Handles exceptions gracefully and returns an appropriate error response.
        """

        # Extract latitude and longitude from query parameters.
        latitude = request.GET.get("latitude")
        longitude = request.GET.get("longitude")

        # Check if both latitude and longitude are provided.
        if not latitude or not longitude:
            return Response(
                {"message": "Latitude and Longitude are required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        # Retrieve API key from settings and create a geolocator.
        try:
            api_key = settings.GOOGLE_MAP_API_KEY
            geolocator = GoogleV3(api_key=api_key)

            # Perform reverse geocoding to find the address.
            location = geolocator.reverse(f"{latitude}, {longitude}")

            # Return address information if found, otherwise indicate not found.
            if location:
                response = {"address": location.address}
                return Response(response, status=status.HTTP_200_OK)
            return Response(
                {"message": "Address not found"},
                status=status.HTTP_404_NOT_FOUND,
            )

        # Handle any exceptions and return an appropriate error response.
        except Exception as e:
            return Response(
                {"message": "Address not found"},
                status=status.HTTP_404_NOT_FOUND,
            )


class GetLatLngFromAddressAPIView(APIView):
    """
    API view to retrieve latitude and longitude from an address.

    This API endpoint expects GET requests with an address query parameter.
    - Required parameter:
        - address: Textual description of the address.

    Returns a JSON response with the latitude and longitude coordinates found for the address,
    or an appropriate error message if the request is invalid or the address cannot be found.
    """

    permission_classes = (AllowAny,)
    http_method_names = ["get"]

    def get(self, request, *args, **kwargs):
        """
        Handle GET requests to retrieve latitude and longitude from an address.

        - Retrieves address value from query parameter.
        - Validates that the address is provided.
        - Retrieves API key from settings (assuming `GOOGLE_MAP_API_KEY` is defined).
        - Creates a GoogleV3 geolocator using the API key.
        - Uses the geolocator to perform geocoding on the provided address.
        - Returns a JSON response with the latitude and longitude coordinates if found, otherwise an error message.
        - Handles exceptions gracefully and returns an appropriate error response.
        """

        # Extract address from the query parameter.
        address = request.GET.get("address")

        # Check if the address is provided.
        if not address:
            return Response(
                {"message": "Address param is required"},
                status=status.HTTP_400_BAD_REQUEST,
            )

        # Retrieve API key from settings and create a geolocator.
        try:
            api_key = settings.GOOGLE_MAP_API_KEY
            geolocator = GoogleV3(api_key=api_key)

            # Perform geocoding to find the latitude and longitude.
            location = geolocator.geocode(address)

            # Return latitude and longitude coordinates if found, otherwise indicate not found
            if location:
                response = {
                    "latitude": location.latitude,
                    "longitude": location.longitude,
                }
                return Response(response, status=status.HTTP_200_OK)
            return Response(
                {"message": "Latitude, Longitude not found"},
                status=status.HTTP_404_NOT_FOUND,
            )

        # Handle any exceptions and return an appropriate error response.
        except Exception as e:
            return Response(
                {"message": "Latitude, Longitude not found"},
                status=status.HTTP_404_NOT_FOUND,
            )


class GetPlacesAPIView(APIView):
    """
    API view for retrieving nearby places based on location and optional place type.

    Attributes:
        permission_classes (tuple): Specifies the permissions required to access this view.
        http_method_names (list): A list of HTTP methods that this view supports.

    Methods:
        get(request, *args, **kwargs): Handles GET requests to retrieve nearby places.
    """

    # Specifies the permissions required to access this view
    permission_classes = (AllowAny,)
    # A list of HTTP methods that this view supports
    http_method_names = ["get"]

    def get(self, request, *args, **kwargs):
        # Extract 'search' parameter from GET request
        inout_search = request.GET.get("search")
        if not inout_search:
            # If 'search' parameter is not provided, return empty response
            return Response([], status=status.HTTP_200_OK)

        # Google Places Autocomplete API URL
        url = "https://maps.googleapis.com/maps/api/place/autocomplete/json"
        # Retrieve Google API key from settings
        api_key = settings.GOOGLE_MAP_API_KEY
        # Parameters for the request to Google Places Autocomplete API
        params = {"input": inout_search, "key": api_key}
        # Send GET request to Google Places Autocomplete API
        response = requests.get(url, params=params)
        # Parse response JSON
        place_data = response.json()

        # Check if request to Google Places Autocomplete API was successful and status is 'OK'
        if response.status_code == 200 and place_data.get("status") == "OK":
            if place_data:
                # List to store details of each place
                place_details_data_list = []
                # Iterate over predictions returned by Autocomplete API
                for index, place_details in enumerate(
                    place_data.get("predictions", [])
                ):
                    # Google Place Details API URL
                    detail_url = (
                        "https://maps.googleapis.com/maps/api/place/details/json"
                    )
                    # Parameters for the request to Google Place Details API
                    detail_params = {
                        "key": api_key,
                        "placeid": place_details.get("place_id"),
                        "fields": "place_id,name,formatted_address,geometry,adr_address,formatted_phone_number",
                    }
                    # Send GET request to Google Place Details API
                    detail_response = requests.get(detail_url, params=detail_params)
                    # Parse response JSON
                    place_detail_data = detail_response.json()
                    # Check if request to Google Place Details API was successful and status is 'OK'
                    if (
                        detail_response.status_code == 200
                        and place_detail_data.get("status") == "OK"
                    ):
                        # Append details of the place to the list
                        place_details_data_list.append(place_detail_data.get("result"))

                # Return list of place details as response
                return Response(place_details_data_list)
            # If no predictions found, return empty response
            return Response([], status=status.HTTP_200_OK)
        else:
            # If request to Google Places Autocomplete API fails, return empty response
            return Response([], status=status.HTTP_200_OK)
