import requests
import time
import re
import csv
import os
import multiprocessing # Import multiprocessing module
from dotenv import load_dotenv

# Load environment variables from .env file
load_dotenv()

# --- Configuration for Lyrics API ---
# Using Lyrics.ovh API - no API key required for basic usage
LYRICS_API_BASE_URL = "https://api.lyrics.ovh/v1"

USER_AGENT = "SoFreshLyricsFetcher/1.0 +https://github.com/yourusername/sofresh" # Updated User-Agent

# --- Helper Function to fetch lyrics ---
def get_lyrics(artist_name, track_name):
    """
    Fetches lyrics for a given artist and track name using Lyrics.ovh API.
    """
    # Lyrics.ovh expects artist/title in the URL path
    # URL encode the artist and track names to handle special characters
    encoded_artist = requests.utils.quote(artist_name)
    encoded_track = requests.utils.quote(track_name)
    
    url = f"{LYRICS_API_BASE_URL}/{encoded_artist}/{encoded_track}"
    
    headers = {
        "User-Agent": USER_AGENT
    }

    try:
        response = requests.get(url, headers=headers)
        response.raise_for_status() # Raise an exception for HTTP errors (4xx or 5xx)
        lyrics_data = response.json()
        
        if lyrics_data and lyrics_data.get("lyrics"):
            return lyrics_data["lyrics"], "Found"
        else:
            return None, "Not Found"
    except requests.exceptions.RequestException as e:
        # print(f"Error fetching lyrics for '{track_name}' by '{artist_name}': {e}") # Suppress for cleaner multiprocessing output
        return None, "API Error"
    except json.JSONDecodeError:
        # print(f"Error parsing JSON response for lyrics for '{track_name}' by '{artist_name}'.") # Suppress
        return None, "JSON Error"

# --- Helper function to clean names by removing content in parentheses ---
def _clean_name(name):
    """Removes content within parentheses from a string."""
    return re.sub(r'\s*\(.*\)\s*', '', name).strip()

# --- Worker function for multiprocessing ---
def process_track_for_lyrics(row):
    """
    Processes a single track row to add lyrics,
    including a retry with cleaned names if initial search fails.
    This function will be run by each worker process.
    """
    track_name = row.get("Track Name", "")
    artist_name = row.get("Artist", "")

    if not track_name or not artist_name:
        return row, f"Skipped: Missing Track Name or Artist in row: {row}"

    lyrics = row.get("Lyrics", "N/A") # Get existing lyrics if available

    initial_lyrics_status = "Already Found" if lyrics != "N/A" else "Not Searched"

    # --- Initial Search ---
    if lyrics == "N/A":
        lyrics, initial_lyrics_status = get_lyrics(artist_name, track_name)
        row["Lyrics"] = lyrics if lyrics else "N/A"

    # --- Retry with Cleaned Names if lyrics are still "N/A" ---
    retry_status_lyrics = ""

    if lyrics == "N/A":
        cleaned_track_name = _clean_name(track_name)
        cleaned_artist_name = _clean_name(artist_name)
        
        # Only retry if cleaning actually changed the name
        if cleaned_track_name != track_name or cleaned_artist_name != artist_name:
            lyrics, retry_status_lyrics = get_lyrics(cleaned_artist_name, cleaned_track_name)
            row["Lyrics"] = lyrics if lyrics else "N/A"
            if lyrics != "N/A":
                retry_status_lyrics = "Found (Cleaned)"
            else:
                retry_status_lyrics = "Not Found (Cleaned)"

    # Construct the final status message
    final_lyrics_status = "Found" if lyrics != "N/A" else initial_lyrics_status if initial_lyrics_status != "Not Searched" else retry_status_lyrics if retry_status_lyrics else "Not Found"

    status_message = (
        f"Processed lyrics for '{track_name}' by '{artist_name}'. "
        f"Lyrics: {final_lyrics_status}"
    )
    
    # Add a small delay to be kind to the API, though Lyrics.ovh is generally robust
    time.sleep(0.05) 

    return row, status_message

# --- Main Script to Process CSV and Add Lyrics ---
def add_lyrics_to_csv(input_csv_file, output_csv_file):
    """
    Reads an existing CSV, searches for lyrics for each track,
    and writes the updated data to a new CSV file using multiprocessing.
    """
    print(f"\n--- Script: Adding Lyrics to '{input_csv_file}' ---")
    
    tracks_to_process = []
    original_headers = []

    try:
        with open(input_csv_file, mode='r', newline='', encoding='utf-8') as infile:
            reader = csv.DictReader(infile)
            original_headers = reader.fieldnames
            for row in reader:
                tracks_to_process.append(row)

    except FileNotFoundError:
        print(f"Error: Input CSV file '{input_csv_file}' not found. Please run the previous scripts first.")
        return
    except Exception as e:
        print(f"An error occurred while reading the input CSV: {e}")
        return

    if not tracks_to_process:
        print("No tracks found in the input CSV to process.")
        return

    # Define new headers including the "Lyrics" column if it doesn't already exist
    new_headers = original_headers[:] # Make a copy
    if "Lyrics" not in new_headers:
        new_headers.append("Lyrics")

    # Determine the number of processes to use
    num_processes = multiprocessing.cpu_count() # Uses all available CPU cores
    # num_processes = 8 # Example: set to a fixed number of processes
    print(f"Using {num_processes} processes to fetch lyrics...")

    tracks_with_lyrics_data = []
    # Use a multiprocessing Pool to distribute the work
    with multiprocessing.Pool(processes=num_processes) as pool:
        # Use imap_unordered for potentially faster results and to process them as they complete
        for i, (processed_row, status_message) in enumerate(pool.imap_unordered(process_track_for_lyrics, tracks_to_process)):
            tracks_with_lyrics_data.append(processed_row)
            if (i + 1) % 10 == 0 or (i + 1) == len(tracks_to_process): # Print every 10 tracks or at the end
                print(f"Progress: {i + 1}/{len(tracks_to_process)} tracks processed. Last: {status_message}")

    print(f"\n--- Writing updated data to '{output_csv_file}' ---")
    try:
        with open(output_csv_file, mode='w', newline='', encoding='utf-8') as outfile:
            writer = csv.DictWriter(outfile, fieldnames=new_headers)
            writer.writeheader()
            writer.writerows(tracks_with_lyrics_data)
        print(f"Successfully saved {len(tracks_with_lyrics_data)} tracks with lyrics to '{output_csv_file}'")
    except IOError as e:
        print(f"Error saving data to CSV file: {e}")

    print("-" * 50)

# --- Main Execution ---
if __name__ == "__main__":
    # This is important for multiprocessing on Windows.
    # It ensures that the child processes import the main module correctly.
    multiprocessing.freeze_support() 
    
    # Input CSV should be the output of the previous script
    input_csv = "so_fresh_compilation_tracks_with_media.csv" 
    # New output CSV for lyrics
    output_csv = "so_fresh_compilation_tracks_with_media_and_lyrics.csv" 

    add_lyrics_to_csv(input_csv, output_csv)
