import os
import time
import csv
import traceback
from datetime import datetime
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoSuchElementException, TimeoutException
from selenium.webdriver.support import expected_conditions as EC
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from IMDBTraktSyncer import errorHandling as EH
from IMDBTraktSyncer import errorLogger as EL

class PageLoadException(Exception):
    pass

def generate_imdb_exports(driver, wait, directory, sync_watchlist_value, sync_ratings_value, sync_watch_history_value, remove_watched_from_watchlists_value, mark_rated_as_watched_value):
    # Generate IMDB .csv exports
  
    # Generate watchlist export if sync_watchlist_value is True
    if sync_watchlist_value or remove_watched_from_watchlists_value:
        success, status_code, url, driver, wait = EH.get_page_with_retries('https://www.imdb.com/list/watchlist', driver, wait)
        if not success:
            # Page failed to load, raise an exception
            raise PageLoadException(f"Failed to load page. Status code: {status_code}. URL: {url}")

        try:
            export_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "div[data-testid*='hero-list-subnav-export-button'] button")))
            # Scroll into view and click the button
            driver.execute_script("arguments[0].scrollIntoView(true);", export_button)
            wait.until(EC.visibility_of(export_button))
            driver.execute_script("arguments[0].click();", export_button)
            time.sleep(3)
        except TimeoutException:
            # print("Export button not found, possibly because the list is empty.")
            pass
    
    # Generate ratings export if sync_ratings_value is True
    if sync_ratings_value or mark_rated_as_watched_value:
        success, status_code, url, driver, wait = EH.get_page_with_retries('https://www.imdb.com/list/ratings', driver, wait)
        if not success:
            # Page failed to load, raise an exception
            raise PageLoadException(f"Failed to load page. Status code: {status_code}. URL: {url}")
        try:
            export_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "div[data-testid*='hero-list-subnav-export-button'] button")))
            # Scroll into view and click the button
            driver.execute_script("arguments[0].scrollIntoView(true);", export_button)
            wait.until(EC.visibility_of(export_button))
            driver.execute_script("arguments[0].click();", export_button)
            time.sleep(3)
        except TimeoutException:
            # print("Export button not found, possibly because the list is empty.")
            pass
    
    # Generate checkins export if sync_watch_history_value is True
    if sync_watch_history_value or remove_watched_from_watchlists_value or mark_rated_as_watched_value:
        success, status_code, url, driver, wait = EH.get_page_with_retries('https://www.imdb.com/list/checkins', driver, wait)
        if not success:
            # Page failed to load, raise an exception
            raise PageLoadException(f"Failed to load page. Status code: {status_code}. URL: {url}")

        try:
            export_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "div[data-testid*='hero-list-subnav-export-button'] button")))
            driver.execute_script("arguments[0].scrollIntoView(true);", export_button)
            wait.until(EC.visibility_of(export_button))
            driver.execute_script("arguments[0].click();", export_button)
            time.sleep(3)
        except TimeoutException:
            # print("Export button not found, possibly because the list is empty.")
            pass
    
    # Wait for export processing to finish
    # Function to check if any summary item contains "in progress"
    def check_in_progress(summary_items):
        for item in summary_items:
            if "in progress" in item.text.lower():
                return True
        return False
    # Maximum time to wait in seconds
    max_wait_time = 1200
    start_time = time.time()

    while time.time() - start_time < max_wait_time:
        # Wait for export processing to finish
        success, status_code, url, driver, wait = EH.get_page_with_retries('https://www.imdb.com/exports/', driver, wait)
        if not success:
            # Page failed to load, raise an exception
            raise PageLoadException(f"Failed to load page. Status code: {status_code}. URL: {url}")
        
        try:
            # Locate all elements with the selector
            summary_items = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".ipc-metadata-list-summary-item")))
        except TimeoutException:
            print("No items found when attempting to generate IMDB exports. Assuming no IMDB watchlist, ratings or check-ins to download.")
            break

        # Check if any summary item contains "in progress"
        if not check_in_progress(summary_items):
            #print("No 'in progress' found. Proceeding.")
            break
        else:
            #print("'In progress' found. Waiting for 30 seconds before retrying.")
            time.sleep(30)
    else:
        raise TimeoutError("IMDB data processing did not complete within the allotted 20 minutes.")
    
    return driver, wait
    
def grant_permissions_and_rename_file(src_path, dest_name):
    """
    Grant full permissions to the file and rename it to the given name.
    :param src_path: Path to the downloaded file.
    :param dest_name: New name for the file (e.g., 'ratings.csv').
    """
    try:
        # Grant full permissions
        os.chmod(src_path, 0o777)
        
        # Rename the file
        dest_path = os.path.join(os.path.dirname(src_path), dest_name)
        os.rename(src_path, dest_path)
    except Exception as e:
        print(f"Error while renaming file {src_path} to {dest_name}: {e}")
        
def download_imdb_exports(driver, wait, directory, sync_watchlist_value, sync_ratings_value, sync_watch_history_value, remove_watched_from_watchlists_value, mark_rated_as_watched_value):
    """
    Download IMDB Exports and rename files with correct permissions.
    """
    # Load page
    success, status_code, url, driver, wait = EH.get_page_with_retries('https://www.imdb.com/exports/', driver, wait)
    if not success:
        # Page failed to load, raise an exception
        raise PageLoadException(f"Failed to load page. Status code: {status_code}. URL: {url}")

    # Locate all export blocks
    try:
        summary_items = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".ipc-metadata-list-summary-item")))
    except TimeoutException:
        print("No items found when attempting to download IMDB exports. Assuming no IMDB watchlist, ratings or check-ins to download.")
        return driver, wait

    # Helper function to find buttons for CSV downloads
    def find_button(item_text):
        for item in summary_items:
            if item_text.lower() in item.text.lower():
                try:
                    button = item.find_element(By.CSS_SELECTOR, "button[data-testid*='export-status-button']")
                    return button
                except Exception:
                    print(f"Export button not found for '{item_text}' on IMDB exports page https://www.imdb.com/exports/")
                    return None
        print(f"No section found matching '{item_text}' on IMDB exports page https://www.imdb.com/exports/")
        return None

    # Find download buttons
    watchlist_csv_link = find_button("watchlist") if sync_watchlist_value or remove_watched_from_watchlists_value else None
    ratings_csv_link = find_button("ratings") if sync_ratings_value or mark_rated_as_watched_value else None
    checkins_csv_link = find_button("check-ins") if sync_watch_history_value or remove_watched_from_watchlists_value or mark_rated_as_watched_value else None

    # Clear any previous csv files
    for file in os.listdir(directory):
        if file.endswith('.csv'):
            os.remove(os.path.join(directory, file))

    # Download each file and rename accordingly
    file_mappings = [
        (watchlist_csv_link, "watchlist.csv"),
        (ratings_csv_link, "ratings.csv"),
        (checkins_csv_link, "checkins.csv")
    ]
    
    for csv_link, file_name in file_mappings:
        if csv_link:
            try:
                driver.execute_script("arguments[0].scrollIntoView(true);", csv_link)
                wait.until(EC.visibility_of(csv_link))
                driver.execute_script("arguments[0].click();", csv_link)
                time.sleep(10)  # wait for download to complete

                downloaded_files = sorted(
                    [os.path.join(directory, f) for f in os.listdir(directory) if f.endswith('.csv')],
                    key=os.path.getmtime,
                    reverse=True
                )

                if downloaded_files:
                    grant_permissions_and_rename_file(downloaded_files[0], file_name)
                else:
                    print(f"Unable to locate downloaded file for {file_name}")
            except Exception as e:
                print(f"Failed to download or rename file '{file_name}': {str(e)}")
        else:
            print(f"No export button found for '{file_name}', skipping...")

    return driver, wait

def get_imdb_watchlist(driver, wait, directory):
    # Get IMDB Watchlist Items
    imdb_watchlist = []
    imdb_watchlist_size = 0
    try:          
        # Look for 'watchlist.csv'
        watchlist_filename = 'watchlist.csv'
        watchlist_path = os.path.join(directory, watchlist_filename)

        if not os.path.exists(watchlist_path):
            raise FileNotFoundError(f"IMDB watchlist data not found. '{watchlist_filename}' not found in the directory")
        
        # Open and process the 'watchlist.csv' file
        with open(watchlist_path, 'r', encoding='utf-8') as file:
            reader = csv.reader(file)
            header = next(reader)  # Read the header row

            # Create a mapping from header names to their index
            header_index = {column_name: index for index, column_name in enumerate(header)}
            
            required_columns = ["Title", "Year", "Const", "Created", "Title Type"]
            missing_columns = [col for col in required_columns if col not in header_index]
            if missing_columns:
                raise ValueError(f"Required columns missing from CSV file: {', '.join(missing_columns)}")

            for row in reader:
                imdb_watchlist_size += 1
                title = row[header_index['Title']]
                year = row[header_index['Year']]
                year = int(year) if year else None
                imdb_id = row[header_index['Const']]
                date_added = row[header_index['Created']]
                media_type = row[header_index['Title Type']]
                # Convert date format
                date_added = datetime.strptime(date_added, '%Y-%m-%d').strftime('%Y-%m-%dT%H:%M:%S.000Z')
                if media_type in ["TV Series", "TV Mini Series"]:
                    media_type = "show"
                elif media_type == "TV Episode":
                    media_type = "episode"
                elif media_type in ["Movie", "TV Special", "TV Movie", "TV Short", "Video"]:
                    media_type = "movie"
                else:
                    media_type = "unknown"

                if media_type != "unknown":
                    imdb_watchlist.append({
                        'Title': title,
                        'Year': year,
                        'IMDB_ID': imdb_id,
                        'Date_Added': date_added,
                        'Type': media_type
                    })
    
        # Delete 'watchlist.csv'
        if os.path.exists(watchlist_path):
            os.remove(watchlist_path)
        
    except FileNotFoundError as e:
        imdb_watchlist = []
        imdb_watchlist_size = 0
        error_message = f"{watchlist_filename} file not found. Assuming the IMDB list is empty."
        print(error_message)
        EL.logger.error(error_message, exc_info=True)
        """
        error_message = str(e)
        print(f"Error: {error_message}")
        traceback.print_exc()
        EL.logger.error(error_message, exc_info=True)
        """
    
    except (NoSuchElementException, TimeoutException):
        # No IMDB Watchlist Items
        imdb_watchlist = []
        imdb_watchlist_size = 0
    
    return imdb_watchlist, imdb_watchlist_size, driver, wait

def get_imdb_ratings(driver, wait, directory):
    # Get IMDB Ratings
    imdb_ratings = []
    try:
        # Look for 'ratings.csv'
        ratings_filename = 'ratings.csv'
        ratings_path = os.path.join(directory, ratings_filename)

        if not os.path.exists(ratings_path):
            raise FileNotFoundError(f"IMDB ratings data not found. '{ratings_filename}' not found in the directory")
        
        # Open and process the 'ratings.csv' file
        with open(ratings_path, 'r', encoding='utf-8') as file:
            reader = csv.reader(file)
            header = next(reader)  # Read the header row

            # Create a mapping from header names to their index
            header_index = {column: index for index, column in enumerate(header)}
            
            required_columns = ["Title", "Year", "Your Rating", "Const", "Date Rated", "Title Type"]
            missing_columns = [col for col in required_columns if col not in header_index]
            if missing_columns:
                raise ValueError(f"Required columns missing from CSV file: {', '.join(missing_columns)}")

            for row in reader:
                title = row[header_index['Title']]
                year = row[header_index['Year']]
                year = int(year) if year else None
                rating = row[header_index['Your Rating']]
                imdb_id = row[header_index['Const']]
                date_added = row[header_index['Date Rated']]
                watched_at = row[header_index['Date Rated']]
                media_type = row[header_index['Title Type']]
                # Convert date format
                date_added = datetime.strptime(date_added, '%Y-%m-%d').strftime('%Y-%m-%dT%H:%M:%S.000Z')
                if media_type == "TV Series" or media_type == "TV Mini Series":
                    media_type = "show"
                elif media_type == "TV Episode":
                    media_type = "episode"
                elif media_type in ["Movie", "TV Special", "TV Movie", "TV Short", "Video"]:
                    media_type = "movie"
                else:
                    media_type = "unknown"
                
                # Append to the list
                if media_type != "unknown":
                    imdb_ratings.append({
                        'Title': title,
                        'Year': year,
                        'Rating': int(rating),
                        'IMDB_ID': imdb_id,
                        'Date_Added': date_added,
                        'WatchedAt': date_added,
                        'Type': media_type
                    })
        
        # Delete 'ratings.csv'
        if os.path.exists(ratings_path):
            os.remove(ratings_path)
        
    except FileNotFoundError as e:
        error_message = f"{ratings_filename} file not found. Assuming the IMDB list is empty."
        print(error_message)
        EL.logger.error(error_message, exc_info=True)
        """
        error_message = str(e)
        print(f"Error: {error_message}")
        traceback.print_exc()
        EL.logger.error(error_message, exc_info=True)
        """
    
    except (NoSuchElementException, TimeoutException):
        # No IMDB Ratings Items
        imdb_ratings = []
        pass
    
    return imdb_ratings, driver, wait
    
def get_imdb_checkins(driver, wait, directory):
    # Get IMDB Check-ins
    imdb_checkins = []
    imdb_checkins_size = 0
    try:
        # Look for 'checkins.csv'
        checkins_filename = 'checkins.csv'
        checkins_path = os.path.join(directory, checkins_filename)

        if not os.path.exists(checkins_path):
            raise FileNotFoundError(f"IMDB Check-ins data not found. '{checkins_filename}' not found in the directory")
        
        # Open and process the 'checkins.csv' file
        with open(checkins_path, 'r', encoding='utf-8') as file:
            reader = csv.reader(file)
            header = next(reader)  # Read the header row

            # Create a mapping from header names to their index
            header_index = {column: index for index, column in enumerate(header)}
            
            required_columns = ["Title", "Year", "Const", "Created", "Title Type"]
            missing_columns = [col for col in required_columns if col not in header_index]
            if missing_columns:
                raise ValueError(f"Required columns missing from CSV file: {', '.join(missing_columns)}")

            for row in reader:
                imdb_checkins_size += 1
                title = row[header_index['Title']]
                year = row[header_index['Year']]
                year = int(year) if year else None
                imdb_id = row[header_index['Const']]
                date_added = row[header_index['Created']]
                media_type = row[header_index['Title Type']]
                # Convert date format
                date_added = datetime.strptime(date_added, '%Y-%m-%d').strftime('%Y-%m-%dT%H:%M:%S.000Z')
                if media_type in ["TV Series", "TV Mini Series"]:
                    media_type = "show"
                elif media_type == "TV Episode":
                    media_type = "episode"
                elif media_type in ["Movie", "TV Special", "TV Movie", "TV Short", "Video"]:
                    media_type = "movie"
                else:
                    media_type = "unknown"

                if media_type != "unknown":
                    imdb_checkins.append({
                        'Title': title,
                        'Year': year,
                        'IMDB_ID': imdb_id,
                        'Date_Added': date_added,
                        'WatchedAt': date_added,
                        'Type': media_type
                    })
                        
        # Delete 'checkins.csv'
        if os.path.exists(checkins_path):
            os.remove(checkins_path)
        
    except FileNotFoundError as e:
        imdb_checkins = []
        imdb_checkins_size = 0
        error_message = f"{checkins_filename} file not found. Assuming the IMDB list is empty."
        print(error_message)
        EL.logger.error(error_message, exc_info=True)
        """
        error_message = str(e)
        print(f"Error: {error_message}")
        traceback.print_exc()
        EL.logger.error(error_message, exc_info=True)
        """
    
    except (NoSuchElementException, TimeoutException):
        # No IMDB Check-in Items
        imdb_checkins = []
        imdb_checkins_size = 0
    
    return imdb_checkins, imdb_checkins_size, driver, wait
            
def get_media_type(imdb_id):
    url = f"https://api.trakt.tv/search/imdb/{imdb_id}"
    response = EH.make_trakt_request(url)
    if response:
        results = response.json()
        if results:
            media_type = results[0].get('type')
            return media_type
    return None

def get_imdb_reviews(driver, wait, directory):
    #Get IMDB Reviews
    
    # Load page
    success, status_code, url, driver, wait = EH.get_page_with_retries('https://www.imdb.com/profile', driver, wait)
    if not success:
        # Page failed to load, raise an exception
        raise PageLoadException(f"Failed to load page. Status code: {status_code}. URL: {url}")
    
    reviews = []
    errors_found_getting_imdb_reviews = False
    try:
        # Wait until the current page URL contains the string "user/"
        wait.until(lambda driver: "user/" in driver.current_url)
        
        # Copy the full URL to a variable and append reviews to it
        reviews_url = driver.current_url + "reviews/"
        
        # Load page
        success, status_code, url, driver, wait = EH.get_page_with_retries(reviews_url, driver, wait)
        if not success:
            # Page failed to load, raise an exception
            raise PageLoadException(f"Failed to load page. Status code: {status_code}. URL: {url}")
            
        while True:
            try:
                try:
                    review_elements = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, "div[data-testid='review-card-parent']")))
                except (NoSuchElementException, TimeoutException):
                    # No review elements found. There are no reviews. Exit the loop without an error.
                    error_message = "No review elements found. There are no reviews. Exit the loop without an error."
                    EL.logger.error(error_message, exc_info=True)
                    break
                
                for element in review_elements:
                    review = {}
                    # Extract review details
                    review['Title'] = element.find_element(By.CSS_SELECTOR, "div[data-testid='review-title-header'] h3 span").text.strip()
                    date_text = element.find_element(By.CSS_SELECTOR, "li.review-date").text
                    review['Year'] = int(date_text.strip().split()[-1]) if date_text else None
                    review['IMDB_ID'] = element.find_element(By.CSS_SELECTOR, "div[data-testid='review-summary'] a").get_attribute("href").split('/')[4]
                    review['IMDBReviewID'] = element.find_element(By.CSS_SELECTOR, "div[data-testid='review-summary'] a").get_attribute("href").split('/')[6]
                    review['Comment'] = element.find_element(By.CSS_SELECTOR, "div[data-testid='review-overflow']").text.strip()
                    spoiler_warning_elements = element.find_elements(By.CSS_SELECTOR, ".review-spoiler-button")
                    review['Spoiler'] = len(spoiler_warning_elements) > 0
                    # Get the media type using Trakt API
                    media_type = get_media_type(review['IMDB_ID'])
                    if media_type:
                        review['Type'] = media_type
                    else:
                        review['Type'] = 'unknown'

                    # if review['Type'] != 'unknown':
                    reviews.append(review)
                    
                try:
                    # Check if "Next" link exists
                    next_link = driver.find_element(By.CSS_SELECTOR, "div[data-testid='index-pagination-nxt']")
                    # Get the 'aria-disabled' attribute
                    is_disabled = next_link.get_attribute("aria-disabled")
                    
                    if is_disabled is None or is_disabled == "true":
                        break  # No more pages, exit the loop
                    
                    # Get the current url before clicking the "Next" link
                    current_url = driver.current_url
                    
                    # Click the "Next" link
                    # Scroll into view and click the button
                    driver.execute_script("arguments[0].scrollIntoView(true);", next_link)
                    wait.until(EC.visibility_of(next_link))
                    driver.execute_script("arguments[0].click();", next_link)

                    # Wait until the URL changes
                    wait.until(lambda driver: driver.current_url != current_url)
                    
                    # Refresh review_elements
                    review_elements = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, "div[data-testid='review-card-parent']")))

                except NoSuchElementException:
                    # "Next" link not found on IMDB reviews page, exit the loop without error
                    error_message = '"Next" link not found on IMDB reviews page. Exiting the loop without error.'
                    EL.logger.warning(error_message, exc_info=True)
                    break
                except TimeoutException:
                    # Timed out waiting for URL change or next page review elements on IMDB reviews page
                    error_message = 'Timed out waiting for URL change or next page review elements on IMDB reviews page. Exiting the loop without error.'
                    EL.logger.error(error_message, exc_info=True)
                    break
            except Exception as e:
                errors_found_getting_imdb_reviews = True
                error_message = f"Exception occurred while getting IMDB reviews: {type(e)}"
                print(f"{error_message}")
                EL.logger.error(error_message, exc_info=True)
                break
    
    except Exception as e:
        errors_found_getting_imdb_reviews = True
        error_message = f"Exception occurred while getting IMDB reviews: {type(e)}"
        print(f"{error_message}")
        traceback.print_exc()
        EL.logger.error(error_message, exc_info=True)
    
    # Filter out duplicate reviews for the same item based on ID
    filtered_reviews = []
    seen = set()
    for item in reviews:
        if item['IMDB_ID'] not in seen:
            seen.add(item['IMDB_ID'])
            filtered_reviews.append(item)
    imdb_reviews = filtered_reviews
    
    return imdb_reviews, errors_found_getting_imdb_reviews, driver, wait