import json
import re
import csv
import os

def safe_get(data, *keys):
    """
    Safely retrieves nested data from a dictionary or list using a sequence of keys/indices.
    Returns None if any key/index is not found or if the data structure is invalid.
    """
    current = data
    for key in keys:
        try:
            if isinstance(current, list):
                if isinstance(key, int) and 0 <= key < len(current):
                    current = current[key]
                else:
                    return None
            elif isinstance(current, dict):
                if key in current:
                    current = current[key]
                else:
                    
                    return None
            else:
             
                return None
        except (IndexError, TypeError, KeyError) as e:
            return None
    return current

def extract_initial_json(html_content):
    """
    Extracts the JSON string assigned to window.APP_INITIALIZATION_STATE from HTML content.
    """
    try:
        match = re.search(r';window\.APP_INITIALIZATION_STATE\s*=\s*(.*?);window\.APP_FLAGS', html_content, re.DOTALL)
        if match:
            json_str = match.group(1)
            if json_str.strip().startswith(('[', '{')):
                return json_str
            else:
                print("Extracted content doesn't look like valid JSON start.")
                return None
        else:
            print("APP_INITIALIZATION_STATE pattern not found.")
            return None
    except Exception as e:
        print(f"Error extracting JSON string: {e}")
        return None

def parse_json_data(json_str):
    """
    Parses the extracted JSON string, handling the nested JSON string if present.
    Returns the main data blob (list) or None if parsing fails or structure is unexpected.
    """
    if not json_str:
        return None
    try:
        initial_data = json.loads(json_str)

        # Check the initial heuristic path [3][6]
        if isinstance(initial_data, list) and len(initial_data) > 3 and isinstance(initial_data[3], list) and len(initial_data[3]) > 6:
             data_blob_or_str = initial_data[3][6]

             # Case 1: It's already the list we expect (older format?)
             if isinstance(data_blob_or_str, list):
                 print("Found expected list structure directly at initial_data[3][6].")
                 return data_blob_or_str

             # Case 2: It's the string containing the actual JSON
             elif isinstance(data_blob_or_str, str) and data_blob_or_str.startswith(")]}'\n"):
                 print("Found string at initial_data[3][6], attempting to parse inner JSON.")
                 try:
                     json_str_inner = data_blob_or_str.split(")]}'\n", 1)[1]
                     actual_data = json.loads(json_str_inner)

                     if isinstance(actual_data, list) and len(actual_data) > 6:
                          potential_data_blob = safe_get(actual_data, 6)
                          if isinstance(potential_data_blob, list):
                              print("Returning data blob found at actual_data[6].")
                              return potential_data_blob # This is the main data structure
                          else:
                              print(f"Data at actual_data[6] is not a list, but {type(potential_data_blob)}. Saving inner data for inspection.")
                              # Save actual_data for debugging
                              try:
                                  with open("debug_inner_data.json", "w", encoding="utf-8") as f_inner:
                                      json.dump(actual_data, f_inner, indent=2)
                                  print("...Successfully saved debug_inner_data.json")
                              except Exception as dump_error_inner:
                                  print(f"Error saving inner debug file: {dump_error_inner}")
                              return None # Structure mismatch within inner data
                     else:
                         print(f"Parsed inner JSON is not a list or too short (len <= 6), type: {type(actual_data)}. Saving inner data for inspection.")
                         # Save actual_data for debugging
                         try:
                             with open("debug_inner_data.json", "w", encoding="utf-8") as f_inner:
                                 json.dump(actual_data, f_inner, indent=2)
                             print("...Successfully saved debug_inner_data.json")
                         except Exception as dump_error_inner:
                             print(f"Error saving inner debug file: {dump_error_inner}")
                         return None # Inner JSON structure not as expected

                 except json.JSONDecodeError as e_inner:
                     print(f"Error decoding inner JSON string: {e_inner}")
                     return None
                 except Exception as e_inner_general:
                     print(f"Unexpected error processing inner JSON string: {e_inner_general}")
                     return None

             # Case 3: Data at [3][6] is neither a list nor the expected string
             else:
                 print(f"Parsed JSON structure unexpected at [3][6]. Expected list or prefixed JSON string, got {type(data_blob_or_str)}.")
                 # Save initial_data for debugging
                 print("Attempting to save full structure to debug_initial_data.json...")
                 try:
                     with open("debug_initial_data.json", "w", encoding="utf-8") as f:
                         json.dump(initial_data, f, indent=2)
                     print("...Successfully saved debug_initial_data.json")
                 except Exception as dump_error:
                     print(f"Error saving debug file: {dump_error}")
                 return None # Unexpected structure at [3][6]

        # Case 4: Initial path [3][6] itself wasn't valid
        else:
            print(f"Initial JSON structure not as expected (list[3][6] path not valid). Type: {type(initial_data)}")
            # Save initial_data for debugging
            print("Attempting to save unexpected structure to debug_initial_data.json...")
            try:
                with open("debug_initial_data.json", "w", encoding="utf-8") as f:
                    json.dump(initial_data, f, indent=2)
                print("...Successfully saved debug_initial_data.json")
            except Exception as dump_error:
                print(f"Error saving debug file: {dump_error}")
            return None # Initial structure invalid

    except json.JSONDecodeError as e:
        print(f"Error decoding initial JSON: {e}")
        return None
    except Exception as e:
        print(f"Unexpected error parsing JSON data: {e}")
        return None

def get_main_name(data):
    """Extracts the main name of the place."""
    return safe_get(data, 11)

def get_place_id(data):
    """Extracts the Google Place ID."""
    return safe_get(data, 10) 

def get_gps_coordinates(data):
    """Extracts latitude and longitude."""
    lat = safe_get(data, 9, 2)
    lon = safe_get(data, 9, 3)
    if lat is not None and lon is not None:
        return {"latitude": lat, "longitude": lon}
    return None

def get_complete_address(data):
    """Extracts structured address components and joins them."""
    address_parts = safe_get(data, 2)
    if isinstance(address_parts, list):
        formatted = ", ".join(filter(None, address_parts))
        return formatted if formatted else None
    return None

def get_rating(data):
    """Extracts the average star rating."""
    return safe_get(data, 4, 7)

def get_reviews_count(data):
    """Extracts the total number of reviews."""
    return safe_get(data, 4, 8)

def get_website(data):
    """Extracts the primary website link."""
    return safe_get(data, 7, 0)

def _find_phone_recursively(data_structure):
    """
    Recursively searches a nested list/dict structure for a list containing
    the phone icon URL followed by the phone number string.
    """
    if isinstance(data_structure, list):
        if len(data_structure) >= 2 and \
           isinstance(data_structure[0], str) and "call_googblue" in data_structure[0] and \
           isinstance(data_structure[1], str):
            phone_number_str = data_structure[1]
            standardized_number = re.sub(r'\D', '', phone_number_str)
            if standardized_number:
                return standardized_number

        for item in data_structure:
            found_phone = _find_phone_recursively(item)
            if found_phone:
                return found_phone

    elif isinstance(data_structure, dict):
        for key, value in data_structure.items():
            found_phone = _find_phone_recursively(value)
            if found_phone:
                return found_phone

    return None

def get_phone_number(data_blob):
    """
    Extracts and standardizes the primary phone number by recursively searching
    the data_blob for the phone icon pattern.
    """
    found_phone = _find_phone_recursively(data_blob)
    if found_phone:
        return found_phone
    else:
        return None

def get_categories(data):
    """Extracts the list of categories/types."""
    return safe_get(data, 13)

def get_thumbnail(data):
    """Extracts the main thumbnail image URL."""
    return safe_get(data, 14, 0, 0, 6, 0)

def save_to_csv(data: dict, filename: str):
    file_exists = os.path.isfile(filename)

    with open(filename, mode='a', newline='', encoding='utf-8') as csvfile:
        writer = csv.DictWriter(csvfile, fieldnames=data.keys())

        if not file_exists:
            writer.writeheader()
        
        writer.writerow(data)

########################################################################################################
### Function to save scraped data to CSV file ###
########################################################################################################

def extract_place_data(html_content, google_map_url=None):
    """
    Improved version of extract_place_data to handle missing/misplaced fields.
    Saves cleaned and aligned data into 'extracted_places.csv'.
    """
    json_str = extract_initial_json(html_content)
    if not json_str:
        print("Failed to extract JSON string from HTML.")
        return None

    data_blob = parse_json_data(json_str)
    if not data_blob:
        print("Failed to parse JSON data or find expected structure.")
        return None

    with open("debug_full_place_page.json", "w", encoding="utf-8") as f:
        json.dump(data_blob, f, indent=2)

    place_details = {
        "name": get_main_name(data_blob),
        "place_id": get_place_id(data_blob),
        "coordinates": get_gps_coordinates(data_blob),
        "address": get_complete_address(data_blob),
        "rating": get_rating(data_blob),
        "reviews_count": get_reviews_count(data_blob),
        "categories": get_categories(data_blob),
        "website": get_website(data_blob),
        "phone": get_phone_number(data_blob),
        "thumbnail": get_thumbnail(data_blob),
    }

    coords = place_details.pop("coordinates", {})
    place_details["latitude"] = coords.get("latitude", "NA")
    place_details["longitude"] = coords.get("longitude", "NA")

    if google_map_url:
        place_details["google_map_url"] = google_map_url

    # Data corrections for misparsed fields
    if isinstance(place_details.get("rating"), list):
        place_details["categories"] = place_details.get("rating")
        place_details["rating"] = "NA"

    if isinstance(place_details.get("reviews_count"), str) and place_details["reviews_count"].startswith("http"):
        place_details["website"] = place_details["reviews_count"]
        place_details["reviews_count"] = "NA"

    if isinstance(place_details.get("phone"), list):
        place_details["phone"] = place_details["phone"][0] if place_details["phone"] else "NA"

    # Final save
    save_to_csv(place_details, output_file="extracted_places.csv")
    return place_details

########################################################################################################
########################################################################################################

def save_to_csv(row_data, output_file="C:\\Users\\Prasad\\Desktop\\Code\\Bot_Scraper\\gmaps_scraper_server\\extracted_places.csv"):
    """
    Append a dictionary of row data to the CSV file, writing headers if file doesn't exist.
    Missing fields will be filled with 'NA' to avoid misaligned rows.
    """
    field_order = [
        "name", "place_id", "address", "rating", "reviews_count",
        "categories", "website", "phone", "latitude", "longitude",
        "thumbnail", "google_map_url"
    ]
    row_complete = {key: row_data.get(key, "NA") for key in field_order}
    
    file_exists = os.path.isfile(output_file)
    with open(output_file, mode='a', newline='', encoding='utf-8') as csvfile:
        writer = csv.DictWriter(csvfile, fieldnames=field_order)
        if not file_exists:
            writer.writeheader()
        writer.writerow(row_complete)
        
########################################################################################################
########################################################################################################

# def save_results(row_data, output_file="extracted_places.csv"):
#     """
#     Append a dictionary of row data to the CSV file, writing headers if file doesn't exist.
#     Missing fields will be filled with 'NA' to avoid misaligned rows.
#     """
#     field_order = [
#         "name", "place_id", "address", "rating", "reviews_count",
#         "categories", "website", "phone", "latitude", "longitude",
#         "thumbnail", "google_map_url"
#     ]
#     row_complete = {key: row_data.get(key, "NA") for key in field_order}
    
#     file_exists = os.path.isfile(output_file)
#     with open(output_file, mode='a', newline='', encoding='utf-8') as csvfile:
#         writer = csv.DictWriter(csvfile, fieldnames=field_order)
#         if not file_exists:
#             writer.writeheader()
#         writer.writerow(row_complete)
        
########################################################################################################
########################################################################################################

# Example usage (for testing):
if __name__ == '__main__':
    
    try:
        with open('sample_place.html', 'r', encoding='utf-8') as f:
            sample_html = f.read()

        extracted_info = extract_place_data(sample_html)

        if extracted_info:
            print("Extracted Place Data:")
            print(json.dumps(extracted_info, indent=2))
        else:
            print("Could not extract data from the sample HTML.")

    except FileNotFoundError:
        print("Sample HTML file 'sample_place.html' not found. Cannot run example.")
    except Exception as e:
        print(f"An error occurred during example execution: {e}")