import os
import shutil
import zipfile
import subprocess
import requests
from bs4 import BeautifulSoup
import patoolib
import py7zr
import mimetypes
from flask import Flask, request, send_file, render_template, jsonify
# from werkzeug.utils import secure_filename # No longer directly needed for uploads, but good for cleanup
import uuid
import glob

app = Flask(__name__)

# Configuration
DOWNLOAD_CACHE = os.path.join(os.getcwd(), 'cache')
OUTPUT_FOLDER = os.path.join(os.getcwd(), 'converted')

app.config['DOWNLOAD_CACHE'] = DOWNLOAD_CACHE
app.config['OUTPUT_FOLDER'] = OUTPUT_FOLDER

# Ensure directories exist
os.makedirs(DOWNLOAD_CACHE, exist_ok=True)
os.makedirs(OUTPUT_FOLDER, exist_ok=True)

def generate_fxmanifest(output_path, meta_files):
    """
    Generates fxmanifest.lua based on found meta files.
    """
    manifest_content = []
    manifest_content.append("fx_version 'cerulean'")
    manifest_content.append("game 'gta5'")
    manifest_content.append("")
    
    if meta_files:
        manifest_content.append("files {")
        for meta in meta_files:
            manifest_content.append(f"    '{meta}',")
        manifest_content.append("}")
        manifest_content.append("")
        
        manifest_content.append("data_file 'HANDLING_FILE' 'handling.meta'" if 'handling.meta' in meta_files else "")
        manifest_content.append("data_file 'VEHICLE_METADATA_FILE' 'vehicles.meta'" if 'vehicles.meta' in meta_files else "")
        manifest_content.append("data_file 'CARCOLS_FILE' 'carcols.meta'" if 'carcols.meta' in meta_files else "")
        manifest_content.append("data_file 'VEHICLE_LAYOUTS_FILE' 'vehiclelayouts.meta'" if 'vehiclelayouts.meta' in meta_files else "")
        manifest_content.append("data_file 'CONTENT_UNLOCKING_META_FILE' 'carvariations.meta'" if 'carvariations.meta' in meta_files else "")
        
        # Cleanup empty lines
        manifest_content = [line for line in manifest_content if line]

    with open(os.path.join(output_path, 'fxmanifest.lua'), 'w') as f:
        f.write('\n'.join(manifest_content))

def download_from_gta5mods(url):
    """
    Scrapes GTA5-Mods.com to find the download link and downloads the file.
    Returns the path to the downloaded archive.
    """
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
        'Referer': 'https://www.gta5-mods.com/'
    }
    
    # 1. Get the mod page
    print(f"Scraping: {url}")
    response = requests.get(url, headers=headers)
    response.raise_for_status()
    soup = BeautifulSoup(response.text, 'html.parser')
    
    # 2. Find the download page link
    # Try finding the button by class, or fallback to any link with /download/
    download_btn = soup.select_one('a.btn.btn-primary.btn-lg')
    download_page_url = None
    
    if download_btn and 'download' in download_btn.get('href', ''):
        download_page_url = "https://www.gta5-mods.com" + download_btn['href']
    else:
        # Fallback: Find any link containing /download/
        for a in soup.find_all('a', href=True):
            if '/download/' in a['href']:
                download_page_url = "https://www.gta5-mods.com" + a['href']
                break
                
    if not download_page_url:
        raise Exception("Could not find download button on the page.")
        
    print(f"Download Page: {download_page_url}")
    
    # 3. Get the actual file link from the download page
    response = requests.get(download_page_url, headers=headers)
    response.raise_for_status()
    soup = BeautifulSoup(response.text, 'html.parser')
    
    # Robust method: find ANY link that points to files.gta5-mods.com
    file_url = None
    for a in soup.find_all('a', href=True):
        if 'files.gta5-mods.com' in a['href']:
            file_url = a['href']
            break
            
    if not file_url:
        # Fallback: try finding a button if the direct link search failed
        file_link_tag = soup.select_one('a.btn.btn-primary')
        if file_link_tag and file_link_tag.get('href'):
            file_url = file_link_tag['href']
            
    if not file_url:
        raise Exception("Could not find final download link on the page.")
        
    if not file_url.startswith('http'):
        file_url = "https://www.gta5-mods.com" + file_url
        
    print(f"Downloading file from: {file_url}")

    # 4. Download the file
    file_response = requests.get(file_url, headers=headers, stream=True)
    file_response.raise_for_status()
    
    # ... (header parsing logic remains) ...
    
    # Get filename from headers or url
    filename = None
    if 'Content-Disposition' in file_response.headers:
        cd = file_response.headers['Content-Disposition']
        # Handle filename="name.zip" and filename*=UTF-8''name.zip
        parts = cd.split(';')
        for part in parts:
            if 'filename=' in part:
                filename = part.split('filename=')[1].strip().strip('"')
                break
                
    if not filename:
        filename = file_url.split('/')[-1]
        
    # Ensure extension is present (basic check)
    if not os.path.splitext(filename)[1]:
        # Try to guess from content-type if basic splitext failed
        content_type = file_response.headers.get('Content-Type')
        if content_type:
             guess = mimetypes.guess_extension(content_type)
             if guess: filename += guess

    print(f"Initial filename: {filename}")
    save_path = os.path.join(app.config['DOWNLOAD_CACHE'], filename)
    
    with open(save_path, 'wb') as f:
        for chunk in file_response.iter_content(chunk_size=8192):
            f.write(chunk)
            
    # Determine correct extension by reading file headers (Magic Bytes)
    # This is necessary because gta5-mods sometimes serves files without extensions
    # and patool on Windows fails without them.
    detected_ext = None
    with open(save_path, 'rb') as f:
        header = f.read(10)
        print(f"Header bytes: {header}")
        if header.startswith(b'PK'):
            detected_ext = '.zip'
        elif header.startswith(b'Rar!'):
            detected_ext = '.rar'
        elif header.startswith(b'7z'):
            detected_ext = '.7z'
        elif b'<!DOCTYPE' in header or b'<html' in header or b'<!doc' in header.lower():
            raise Exception("Error: Downloaded file appears to be HTML. The scraper likely failed to find the direct download link.")
            
    # If we detected an extension and the filename doesn't have it, rename!
    if detected_ext:
        current_ext = os.path.splitext(filename)[1].lower()
        if current_ext != detected_ext:
            print(f"Detected archive type: {detected_ext}. Renaming file.")
            new_filename = os.path.splitext(filename)[0] + detected_ext
            new_save_path = os.path.join(app.config['DOWNLOAD_CACHE'], new_filename)
            shutil.move(save_path, new_save_path)
            save_path = new_save_path
            filename = new_filename
    else:
         # Fallback default if recognition failed but we have no extension
         if not os.path.splitext(filename)[1]:
             print("Warning: Unknown header, defaulting to .zip")
             new_save_path = save_path + '.zip'
             shutil.move(save_path, new_save_path)
             save_path = new_save_path
             
    print(f"Final saved file: {save_path}")
    return save_path

def extract_archive(archive_path, extract_to):
    """
    Extracts zip, rar, or 7z archives.
    """
    os.makedirs(extract_to, exist_ok=True)
    try:
        if archive_path.lower().endswith('.7z'):
             print("Extracting 7z archive with py7zr...")
             with py7zr.SevenZipFile(archive_path, mode='r') as z:
                 z.extractall(path=extract_to)
        elif archive_path.lower().endswith('.zip'):
             print("Extracting zip archive with zipfile...")
             with zipfile.ZipFile(archive_path, 'r') as zip_ref:
                zip_ref.extractall(extract_to)
        else:
            # Try patool for everything else (mainly RAR)
            # For RAR to work, 'unrar' must be in PATH or installed
            print(f"Extracting {archive_path} with patool...")
            patoolib.extract_archive(archive_path, outdir=extract_to)
            
    except Exception as e:
        raise Exception(f"Failed to extract archive: {e}. If it's a RAR file, ensure 'unrar' or 'WinRAR' is installed and in your PATH.")

def find_rpf_files(directory):
    """
    Recursively finds all .rpf files in the directory.
    """
    rpf_files = []
    for root, dirs, files in os.walk(directory):
        for file in files:
            if file.lower().endswith('.rpf'):
                rpf_files.append(os.path.join(root, file))
    return rpf_files

def process_rpf(file_path, resource_name, base_extract_dir):
    """
    Extracts a specific RPF file and organizes it.
    New logic: We might have multiple RPFs or nested folders.
    We'll treat "file_path" as the RPF to process.
    """
    # Create a subfolder for this RPF's content inside the final resource folder
    # Actually, usually one mod = one resource. 
    # If a mod has multiple RPFs, we might need a sophisticated join or just pick the 'dlc.rpf'.
    
    # Setup the output directory for this resource
    resource_dir = os.path.join(app.config['OUTPUT_FOLDER'], resource_name)
    if os.path.exists(resource_dir):
        shutil.rmtree(resource_dir)
    os.makedirs(resource_dir)

    temp_extract_path = os.path.join(resource_dir, "_temp_rpf_extracted")
    os.makedirs(temp_extract_path)

    # Define path to GTAUtil.exe in the local bin folder
    gta_util_path = os.path.join(app.root_path, 'bin', 'GTAUtil.exe')
    
    # Check if we are on Windows or Linux
    is_windows = os.name == 'nt'
    
    # Validation
    if not os.path.exists(gta_util_path):
        print(f"GTAUtil not found at {gta_util_path}.")
        # Only fallback if we strictly cannot find the tool
        # But we really should have it. 
        # Create fallback:
        with open(os.path.join(temp_extract_path, 'vehicles.meta'), 'w') as f: f.write('')
        print("Using dummy fallback due to missing tool.")
        return resource_dir

    try:
        # Build command
        # Windows: "GTAUtil.exe extractarchive -i ..."
        # Linux: "dotnet GTAUtil.exe extractarchive -i ..."
        cmd = []
        if not is_windows:
            cmd = ['dotnet']
            
        cmd.extend([gta_util_path, 'extractarchive', '-i', file_path, '-o', temp_extract_path])
        
        print(f"Running GTAUtil: {cmd}")
        # Capture output to debug
        result = subprocess.run(cmd, check=True, capture_output=True, text=True)
        print(result.stdout)
        
    except subprocess.CalledProcessError as e:
        print(f"Error extracting RPF with GTAUtil: {e}")
        print(f"Output: {e.stdout}")
        print(f"Error: {e.stderr}")
        # Fallback dummy
        with open(os.path.join(temp_extract_path, 'vehicles.meta'), 'w') as f: f.write('')
    except Exception as e:
        print(f"Unexpected error extracting RPF: {e}")

    # Organize files
    stream_dir = os.path.join(resource_dir, 'stream')
    os.makedirs(stream_dir, exist_ok=True)
    
    meta_files = []

    for root, dirs, files in os.walk(temp_extract_path):
        for file in files:
            file_lower = file.lower()
            src_path = os.path.join(root, file)
            
            if file_lower.endswith(('.yft', '.ytd', '.ydr')):
                # Move to stream
                shutil.move(src_path, os.path.join(stream_dir, file))
            elif file_lower.endswith(('.meta', '.xml', '.dat')):
                # Move to root
                shutil.move(src_path, os.path.join(resource_dir, file))
                meta_files.append(file)

    shutil.rmtree(temp_extract_path)
    generate_fxmanifest(resource_dir, meta_files)
    
    return resource_dir

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/process', methods=['POST'])
def process_url():
    data = request.json
    url = data.get('url')
    
    if not url or 'gta5-mods.com' not in url:
        return jsonify({'error': 'Invalid URL. Please use a gta5-mods.com link.'}), 400
    
    try:
        # 1. Download Archive
        archive_path = download_from_gta5mods(url)
        
        # 2. Extract Archive to a temp staging area
        unique_id = str(uuid.uuid4())[:8]
        staging_dir = os.path.join(app.config['DOWNLOAD_CACHE'], f"stage_{unique_id}")
        extract_archive(archive_path, staging_dir)
        
        # 3. Find RPF(s)
        # We look specifically for 'dlc.rpf' or utilize the first found .rpf if dlc.rpf is missing
        # Some mods have multiple options (Manual install, Add-on, Replace).
        # We prefer "Add-on" -> "dlc.rpf".
        
        rpf_files = find_rpf_files(staging_dir)
        
        target_rpf = None
        
        # simple heuristic: prefer path with 'addon' or filename 'dlc.rpf'
        for rpf in rpf_files:
            if 'dlc.rpf' in os.path.basename(rpf).lower():
                target_rpf = rpf
                # Check if it's in an 'addon' folder if multiple dlc.rpf exist?
                # For now, pick the first dlc.rpf
                break
        
        if not target_rpf and rpf_files:
            target_rpf = rpf_files[0] # Fallback to first found
            
        if not target_rpf:
             return jsonify({'error': 'No .rpf file found in the downloaded archive.'}), 400
             
        # 4. Process the found RPF
        # Use the mod name from URL or generic name
        resource_name = url.strip('/').split('/')[-1] + "_" + unique_id
        resource_dir = process_rpf(target_rpf, resource_name, staging_dir)
        
        # 5. Zip result
        zip_filename = f"{resource_name}.zip"
        zip_path = os.path.join(app.config['OUTPUT_FOLDER'], zip_filename)
        
        with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
            for root, dirs, files in os.walk(resource_dir):
                for file in files:
                    file_path = os.path.join(root, file)
                    arcname = os.path.relpath(file_path, app.config['OUTPUT_FOLDER'])
                    zipf.write(file_path, arcname)
                    
        # Cleanup staging
        shutil.rmtree(staging_dir)
        if os.path.exists(archive_path):
            os.remove(archive_path)

        return jsonify({'success': True, 'download_url': f'/download/{zip_filename}'})

    except Exception as e:
        print(e)
        return jsonify({'error': str(e)}), 500

@app.route('/download/<filename>')
def download_file(filename):
    return send_file(os.path.join(app.config['OUTPUT_FOLDER'], filename), as_attachment=True)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

