"""

    AUTHOR:

        Khushal P Soonderji

    DATE:

        Friday, 27th Jun., 2025

    OBJECTIVE:

        To get location and business info from Google's Places API.

    REFERENCES:

        1. GMail Quickstart: https://developers.google.com/gmail/api/quickstart/python
        2. GMail Labels: https://developers.google.com/gmail/api/guides/labels
        3. GMail Messages: https://developers.google.com/gmail/api/reference/rest/v1/users.messages
        4. People Profile: https://developers.google.com/people/api/rest/v1/people/get

    DOWNLOADS:

        N/A

"""


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                   IMPORT                                                      ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")

# System-level activities:
import io

# My utils:
from utils_v2.string import json

# My Google utils:
from utils_v2.goog.controllers.base import AsyncGoogleBase
from utils_v2.goog.models.auth_tokens import GoogleAuthTokens
from utils_v2.goog.models.api_call import GoogleApiResponse

# Related to Google:
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build

# To make API calls:
import httpx

# For asynchronous activities:
import asyncio

# To work with date and time:
import datetime

# For working with datatypes:
from typing import Dict, Literal, List, Any

# For debugging:
from icecream import IceCreamDebugger
import inspect

# For computational help:
import math


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                           MACROS / ONE-TIME INIT                                              ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# Google Scopes:
SCOPES_PLACES_FULL = [
    r"https://www.googleapis.com/auth/cloud-platform",
    r"https://www.googleapis.com/auth/userinfo.profile",
    r"https://www.googleapis.com/auth/gmail.metadata"
]


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                VARIABLES                                                      ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# --- Nothing Yet


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                  FUNCTIONS                                                    ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# --- Nothing Yet


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                   CLASSES                                                     ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


class AsyncPlacesClient(AsyncGoogleBase):

    # The types of places:
    PLACE_TYPES = {
        "Automotive": [
            "car_dealer",
            "car_rental",
            "car_repair",
            "car_wash",
            "electric_vehicle_charging_station",
            "gas_station",
            "parking",
            "rest_stop"
        ],
        "Business": [
            "corporate_office",
            "farm",
            "ranch"
        ],
        "Culture": [
            "art_gallery",
            "art_studio",
            "auditorium",
            "cultural_landmark",
            "historical_place",
            "monument",
            "museum",
            "performing_arts_theater",
            "sculpture"
        ],
        "Education": [
            "library",
            "preschool",
            "primary_school",
            "secondary_school",
            "university"
        ],
        "Entertainment and Recreation": [
            "adventure_sports_center",
            "amphitheatre",
            "amusement_center",
            "amusement_park",
            "aquarium",
            "banquet_hall",
            "barbecue_area",
            "botanical_garden",
            "bowling_alley",
            "casino",
            "childrens_camp",
            "comedy_club",
            "community_center",
            "concert_hall",
            "convention_center",
            "cultural_center",
            "cycling_park",
            "dance_hall",
            "dog_park",
            "event_venue",
            "ferris_wheel",
            "garden",
            "hiking_area",
            "historical_landmark",
            "internet_cafe",
            "karaoke",
            "marina",
            "movie_rental",
            "movie_theater",
            "national_park",
            "night_club",
            "observation_deck",
            "off_roading_area",
            "opera_house",
            "park",
            "philharmonic_hall",
            "picnic_ground",
            "planetarium",
            "plaza",
            "roller_coaster",
            "skateboard_park",
            "state_park",
            "tourist_attraction",
            "video_arcade",
            "visitor_center",
            "water_park",
            "wedding_venue",
            "wildlife_park",
            "wildlife_refuge",
            "zoo"
        ],
        "Facilities": [
            "public_bath",
            "public_bathroom",
            "stable"
        ],
        "Finance": [
            "accounting",
            "atm",
            "bank"
        ],
        "Food and Drink": [
            "acai_shop",
            "afghani_restaurant",
            "african_restaurant",
            "american_restaurant",
            "asian_restaurant",
            "bagel_shop",
            "bakery",
            "bar",
            "bar_and_grill",
            "barbecue_restaurant",
            "brazilian_restaurant",
            "breakfast_restaurant",
            "brunch_restaurant",
            "buffet_restaurant",
            "cafe",
            "cafeteria",
            "candy_store",
            "cat_cafe",
            "chinese_restaurant",
            "chocolate_factory",
            "chocolate_shop",
            "coffee_shop",
            "confectionery",
            "deli",
            "dessert_restaurant",
            "dessert_shop",
            "diner",
            "dog_cafe",
            "donut_shop",
            "fast_food_restaurant",
            "fine_dining_restaurant",
            "food_court",
            "french_restaurant",
            "greek_restaurant",
            "hamburger_restaurant",
            "ice_cream_shop",
            "indian_restaurant",
            "indonesian_restaurant",
            "italian_restaurant",
            "japanese_restaurant",
            "juice_shop",
            "korean_restaurant",
            "lebanese_restaurant",
            "meal_delivery",
            "meal_takeaway",
            "mediterranean_restaurant",
            "mexican_restaurant",
            "middle_eastern_restaurant",
            "pizza_restaurant",
            "pub",
            "ramen_restaurant",
            "restaurant",
            "sandwich_shop",
            "seafood_restaurant",
            "spanish_restaurant",
            "steak_house",
            "sushi_restaurant",
            "tea_house",
            "thai_restaurant",
            "turkish_restaurant",
            "vegan_restaurant",
            "vegetarian_restaurant",
            "vietnamese_restaurant",
            "wine_bar"
        ],
        "Geographical Areas": [
            "administrative_area_level_1",
            "administrative_area_level_2",
            "country",
            "locality",
            "postal_code",
            "school_district"
        ],
        "Government": [
            "city_hall",
            "courthouse",
            "embassy",
            "fire_station",
            "government_office",
            "police",
            "post_office"
        ],
        "Health and Wellness": [
            "chiropractor",
            "dental_clinic",
            "dentist",
            "doctor",
            "drugstore",
            "hospital",
            "massage",
            "medical_lab",
            "pharmacy",
            "physiotherapist",
            "sauna",
            "skin_care_clinic",
            "spa",
            "tanning_studio",
            "wellness_center",
            "yoga_studio"
        ],
        "Housing": [
            "apartment_building",
            "apartment_complex",
            "condominium_complex",
            "housing_complex"
        ],
        "Lodging": [
            "bed_and_breakfast",
            "budget_japanese_inn",
            "campground",
            "camping_cabin",
            "cottage",
            "extended_stay_hotel",
            "farmstay",
            "guest_house",
            "hostel",
            "hotel",
            "inn",
            "japanese_inn",
            "lodging",
            "mobile_home_park",
            "motel",
            "private_guest_room",
            "resort_hotel",
            "rv_park"
        ],
        "Natural Features": [
            "beach"
        ],
        "Places of Worship": [
            "church",
            "hindu_temple",
            "mosque",
            "synagogue"
        ],
        "Services": [
            "astrologer",
            "barber_shop",
            "beautician",
            "beauty_salon",
            "body_art_service",
            "catering_service",
            "cemetery",
            "child_care_agency",
            "consultant",
            "courier_service",
            "electrician",
            "florist",
            "food_delivery",
            "foot_care",
            "funeral_home",
            "hair_care",
            "hair_salon",
            "insurance_agency",
            "laundry",
            "lawyer",
            "locksmith",
            "makeup_artist",
            "moving_company",
            "nail_salon",
            "painter",
            "plumber",
            "psychic",
            "real_estate_agency",
            "roofing_contractor",
            "storage",
            "summer_camp_organizer",
            "tailor",
            "telecommunications_service_provider",
            "tour_agency",
            "tourist_information_center",
            "travel_agency",
            "veterinary_care"
        ],
        "Shopping": [
            "asian_grocery_store",
            "auto_parts_store",
            "bicycle_store",
            "book_store",
            "butcher_shop",
            "cell_phone_store",
            "clothing_store",
            "convenience_store",
            "department_store",
            "discount_store",
            "electronics_store",
            "food_store",
            "furniture_store",
            "gift_shop",
            "grocery_store",
            "hardware_store",
            "home_improvement_store",
            "jewelry_store",
            "market",
            "pet_store",
            "shoe_store",
            "shopping_mall",
            "sporting_goods_store",
            "store",
            "supermarket",
            "warehouse_store",
            "wholesaler"
        ],
        "Sports": [
            "arena",
            "athletic_field",
            "fishing_charter",
            "fishing_pond",
            "fitness_center",
            "golf_course",
            "gym",
            "ice_skating_rink",
            "playground",
            "ski_resort",
            "sports_activity_location",
            "sports_club",
            "sports_coaching",
            "sports_complex",
            "stadium",
            "swimming_pool"
        ],
        "Transportation": [
            "airport",
            "airstrip",
            "bus_station",
            "bus_stop",
            "ferry_terminal",
            "heliport",
            "international_airport",
            "light_rail_station",
            "park_and_ride",
            "subway_station",
            "taxi_stand",
            "train_station",
            "transit_depot",
            "transit_station",
            "truck_stop"
        ]
    }

    # Kinds of fields:
    FIELD_MASK_BASIC_INFO = [
        "nextPageToken",
        "places.id",
        "places.name",
        "places.displayName",
        "places.formattedAddress",
        "places.location",
        "places.googleMapsUri",
        "places.photos",
        "places.primaryType",
        "places.types"
    ]
    FIELD_MASK_BUSINESS_LEADS = [
        "nextPageToken",
        "places.id",
        "places.name",
        "places.displayName",
        "places.businessStatus",
        "places.formattedAddress",
        "places.postalAddress",
        "places.location"
        "places.googleMapsUri",
        "places.photos",
        "places.primaryType",
        "places.types",
        "places.regularOpeningHours",
        "places.regularSecondaryOpeningHours",
        "places.rating",
        "places.userRatingCount",
        "places.websiteUri",
        "places.internationalPhoneNumber",
        "places.priceLevel",
        "places.priceRange"
    ]

    # ┳┳       ┏┓    ┏•┓
    # ┃┃┏┏┓┏┓  ┃┃┏┓┏┓╋┓┃┏┓
    # ┗┛┛┗ ┛   ┣┛┛ ┗┛┛┗┗┗

    async def get_user_profile(
            self,
            tokens: GoogleAuthTokens,
            user_id: str = "me",
    ) -> GoogleApiResponse:

        """
        To get the details of the user that has logged in.
        DOCUMENTATION:
            1. https://developers.google.com/gmail/api/reference/rest/v1/users/getProfile
            2. https://developers.google.com/people/api/rest/v1/people/get
            3. https://developers.google.com/people/api/rest/v1/people#Person
        :param tokens: The object that holds the access token to the service.
        :param user_id: The id of the user for which this service must be run. Typically, the e-mail id itself or "me".
        :return: A structured response where the list of labels will be in the 'data' variable.
        """

        # Ensure that the tokens are valid:
        await tokens.arefresh(
            http_client=self._http_client,
            client_id=self._client_id,
            client_secret=self._client_secret,
            force_refresh=False
        )

        # Make the GMail API call:
        if not self._debug_only_errors: self._printer("Getting User Profile.")
        gmail_api_response = await self.get(
            url=f"https://gmail.googleapis.com/gmail/v1/users/{user_id}/profile",
            headers={"Authorization": f"Bearer {tokens.accessToken}"}
        )

        # If the call was successful:
        if gmail_api_response.httpCode in [200]:
            gmail_api_response.success = True
            gmail_api_response.data = await gmail_api_response.get_json()
            gmail_api_response.data["displayName"] = None
            gmail_api_response.data["displayPictureUrl"] = None

        # If the call failed:
        else:
            self._printer(
                gmail_api_response.action,
                gmail_api_response.method,
                gmail_api_response.httpCode,
            )
            return gmail_api_response

        # Make the People API call:
        if not self._debug_only_errors: self._printer("Getting User Profile.")
        people_api_response = await self.get(
            url=f"https://people.googleapis.com/v1/people/me?personFields=names,photos,birthdays,phoneNumbers,genders,emailAddresses,addresses",
            headers={"Authorization": f"Bearer {tokens.accessToken}"}
        )

        # If the call was successful:
        if people_api_response.httpCode in [200]:
            people_api_response.success = True
            people_api_response.data = await people_api_response.get_json()
            for item in people_api_response.data.get("names", []):
                if item["metadata"]["primary"]:
                    gmail_api_response.data["displayName"] = item.get("displayName")
            for item in people_api_response.data.get("photos", []):
                if item["metadata"]["primary"]:
                    gmail_api_response.data["displayPictureUrl"] = item.get("url")

        # If the call failed:
        else:
            self._printer(
                people_api_response.action,
                people_api_response.method,
                people_api_response.httpCode,
            )

        # Done here:
        return gmail_api_response

    # ┏┓           •
    # ┃┃┏┓┏┓┏┓┏┓┏┓╋┓┏┓┏
    # ┣┛┛ ┗┛┣┛┗ ┛ ┗┗┗ ┛
    #       ┛

    @property
    def primary_types(self):
        return list(self.PLACE_TYPES.keys())

    def types_for(self, primary_type: str):
        return self.PLACE_TYPES.get(primary_type)

    # async def _nearby_radius_search(
    #         self,
    #         tokens: GoogleAuthTokens,
    #         latitude: float,
    #         longitude: float,
    #         radius: float,
    #         max_count: int = 100,
    #         next_page_token: str = None,
    # ) -> GoogleApiResponse:

    async def nearby_radius_search(
            self,
            tokens: GoogleAuthTokens,
            latitude: float,
            longitude: float,
            radius: float,
            included_primary_types: List[str] = None,
            included_types: List[str] = None,
            excluded_primary_types: List[str] = None,
            excluded_types: List[str] = None,
            field_mask: List[str] = None,
            max_count: int = 100,
            next_page_token: str = None,
    ) -> GoogleApiResponse:

        """
        https://developers.google.com/maps/documentation/places/web-service/nearby-search
        """

        # Ensure that the tokens are valid:
        await tokens.arefresh(
            http_client = self._http_client,
            client_id = self._client_id,
            client_secret = self._client_secret,
            force_refresh = False
        )
        
        # Create the headers:
        request_headers = {"Authorization": f"Bearer {tokens.accessToken}"}
        if field_mask: request_headers["X-Goog-FieldMask"] = ",".join(field_mask)

        # Start creating the JSON payload based on the inputs:
        request_json = {
            "maxResultCount": max_count,
            "locationRestriction": {
                "circle": {
                    "center": {
                        "latitude": latitude,
                        "longitude": longitude
                    },
                    "radius": radius
                }
            }
        }
        if included_primary_types: request_json["includedPrimaryTypes"] = included_primary_types
        if included_types: request_json["includedTypes"] = included_types
        if excluded_primary_types: request_json["excludedPrimaryTypes"] = excluded_primary_types
        if excluded_types: request_json["excludedTypes"] = excluded_types

        # Make the API call:
        if not self._debug_only_errors: self._printer("Listing Nearby (Radius) Places.")
        api_response = await self.post(
            url = f"https://places.googleapis.com/v1/places:searchNearby",
            headers = request_headers,
            json = request_json
        )

        # If the call was successful:
        if api_response.httpCode in [200]:
            api_response.success = True
            api_response.data = await api_response.get_json()

        # Done here:
        return api_response


    async def text_query_search(
            self,
            tokens: GoogleAuthTokens,
            text_query: str,
            included_primary_types: List[str] = None,
            included_types: List[str] = None,
            excluded_primary_types: List[str] = None,
            excluded_types: List[str] = None,
            field_mask: List[str] = None,
            max_count: int = 100,
            next_page_token: str = None,
    ) -> GoogleApiResponse:

        """
        https://developers.google.com/maps/documentation/places/web-service/text-search
        """

        print("IN TOKEN", tokens)
        print("IN TOKEN", tokens.model_dump())
        print("IN TOKEN", tokens.token["accessToken"])

        # Ensure that the tokens are valid:
        # await tokens.arefresh(
        #     http_client=self._http_client,
        #     client_id=self._client_id,
        #     client_secret=self._client_secret,
        #     force_refresh=False
        # )
        print("RH --")
        # Create the headers:
        request_headers = {"Authorization": f"Bearer {tokens.token['accessToken']}"}
        # if field_mask: request_headers["X-Goog-FieldMask"] = ",".join(field_mask)
        print("RH",request_headers)
        if field_mask: request_headers["X-Goog-FieldMask"] = "places.displayName,places.formattedAddress,places.priceLevel"

        # Start creating the JSON payload based on the inputs:
        # request_json = {
        #     "maxResultCount": max_count,
        #     "locationRestriction": {
        #         "circle": {
        #             "center": {
        #                 "latitude": latitude,
        #                 "longitude": longitude
        #             },
        #             "radius": radius
        #         }
        #     }
        # }

        # Start creating the JSON payload based on the inputs:
        request_json = {
            "maxResultCount": max_count,
            "textQuery": text_query
        }

        # if included_primary_types: request_json["includedPrimaryTypes"] = included_primary_types
        # if included_types: request_json["includedTypes"] = included_types
        # if excluded_primary_types: request_json["excludedPrimaryTypes"] = excluded_primary_types
        # if excluded_types: request_json["excludedTypes"] = excluded_types
        print(request_json)
        # Make the API call:
        if not self._debug_only_errors: self._printer("Listing text Search Places.")
        api_response = await self.post(
            url=f"https://places.googleapis.com/v1/places:searchText",
            headers=request_headers,
            json=request_json
        )

        print(api_response)

        # If the call was successful:
        if api_response.httpCode in [200]:
            api_response.success = True
            api_response.data = await api_response.get_json()

        # Done here:
        return api_response


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                               MAIN PROGRAM                                                    ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


if __name__ == "__main__":

    # Create an HTTP client:
    test_client = httpx.AsyncClient(
        limits = httpx.Limits(
            max_connections = 100,  # ............ Maximum number of connections allowed in the pool.
            max_keepalive_connections = 50,  # ... Maximum number of connections that can be kept alive.
        ),
        timeout = httpx.Timeout(
            connect = 2.5,  # ... Shorter connection timeout.
            read = 2.5,  # ...... Like what EasyEcom gives.
            write = 10.0,  # .... Time to wait for sending data.
            pool = 120.0  # ..... Time to wait for a free connection from the pool.
        )
    )

    # Read the secrets that give you access to the app:
    # secrets_file = r"C:\Users\Khushal P Soonderji\Downloads\google_places_test_secret.json"
    secrets_file = r"/home/python-dev-debug/Downloads/client_secret_349360248417-uuba8eudk75jg1jag212g5obhc1uostk.apps.googleusercontent.com.json"
    secrets_dict = json.from_file(secrets_file)

    async def main():

        # Create an instance of the client:
        my_goog = AsyncPlacesClient(
            service_name = "places",
            oauth_json = secrets_dict,
            http_client = test_client,
            redirect_url = r"https://wtt.ditscentre.in/shopify/test/1",
            debug = True,
            debug_prefix = "Places (M) | ",
            debug_only_errors = False
        )

        # Request Auth:
        print("AUTH URL:", await my_goog.get_authorization_url(
            scopes = SCOPES_PLACES_FULL,
            state = "Bhopli",
            approval_prompt = "force"
        ))

        # Get tokens from callback:
        test_tokens = await my_goog.get_authorization_tokens(
            scopes = SCOPES_PLACES_FULL,
            redirect_url = input("Paste the redirect URL here: ")
        )
        print("TOKENS:", test_tokens)

        # List out the primary and secondary types:
        print("PRIMARY TYPES:", my_goog.primary_types)
        print("'Food and Drink' TYPES:", my_goog.types_for("Food and Drink"))
        print("'Alien Spaceship' TYPES:", my_goog.types_for("Alien Spaceship"))

        response = await my_goog.get_user_profile(
            tokens=test_tokens
        )

        print("SUCCESS:", response.success)
        print("SUMMARY:", response.to_markdown())
        print("\n\n---\n\n")
        print("DATA:", json.to_string(response.data, default=str))
        if not response.success:
            print("\n\n---\n\n")

        # Test some feature:
        response = await my_goog.nearby_radius_search(
            tokens = test_tokens,
            latitude = 19.03145183334671,
            longitude = 72.85437426861148,
            radius = 1_000.0,
            field_mask = None,
            max_count = 5,
        )
        print("SUCCESS:", response.success)
        print("SUMMARY:", response.to_markdown())
        print("\n\n---\n\n")
        print("DATA:", json.to_string(response.data, default = str))
        if not response.success:
            print("\n\n---\n\n")


    asyncio.run(main())
