import typer
import requests
import time
import os
import json
import subprocess
from rich.progress import Progress
from bencodepy import decode, encode
import shutil
import tempfile
import importlib.resources
import urllib.parse
import hashlib
from qbittorrentapi import Client, APIError

app = typer.Typer()

CONFIG_DIR = os.path.expanduser("~/.tormadeeasy")
CONFIG_FILE = os.path.join(CONFIG_DIR, "config.json")

def get_config():
    """Reads and returns the configuration."""
    if not os.path.exists(CONFIG_FILE):
        return {}
    with open(CONFIG_FILE, "r") as f:
        return json.load(f)

def save_config(config):
    """Saves the configuration."""
    os.makedirs(CONFIG_DIR, exist_ok=True)
    with open(CONFIG_FILE, "w") as f:
        json.dump(config, f, indent=4)

def get_ngrok_authtoken():
    """Gets the ngrok authtoken from the config file or prompts the user for it."""
    config = get_config()
    if "ngrok_authtoken" in config and config["ngrok_authtoken"]:
        return config["ngrok_authtoken"]
    
    authtoken = typer.prompt("Please enter your ngrok authtoken")
    config["ngrok_authtoken"] = authtoken
    save_config(config)
    return authtoken

def manage_docker_compose(action: str, ngrok_authtoken: str, upload_dir_host: str, download_dir_host: str, config_dir_host: str):
    """Starts or stops the docker-compose services."""
    with importlib.resources.path("tormadeeasy", "docker-compose.yml") as compose_path:
        env = os.environ.copy()
        env["NGROK_AUTHTOKEN"] = ngrok_authtoken
        env["UPLOAD_DIR_HOST"] = upload_dir_host
        env["DOWNLOAD_DIR_HOST"] = download_dir_host
        env["CONFIG_DIR_HOST"] = config_dir_host
        
        with tempfile.TemporaryDirectory() as tmpdir:
            compose_dest_path = os.path.join(tmpdir, "docker-compose.yml")
            shutil.copy(compose_path, compose_dest_path)

            if action == "up":
                # Ensure a clean start by bringing down any existing services
                print("Ensuring clean Docker Compose environment...")
                try:
                    subprocess.run(
                        ["docker", "compose", "-f", compose_dest_path, "down", "--remove-orphans", "-v"],
                        env=env,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE,
                        cwd=tmpdir,
                        check=False, # Don't raise an exception if down fails
                    )
                except Exception as e:
                    print(f"Warning: docker compose down failed: {e}")
                
                # Explicitly remove potentially conflicting containers
                for container_name in ["qbittorrent-tormadeeasy", "web-server-tormadeeasy", "ngrok-tormadeeasy"]:
                    try:
                        subprocess.run(
                            ["docker", "rm", "-f", container_name],
                            stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE,
                            check=False,
                        )
                    except Exception as e:
                        print(f"Warning: Failed to remove container {container_name}: {e}")

            command = ["docker", "compose", "-f", compose_dest_path, action]
            if action == "up":
                command.append("-d")

            process = subprocess.Popen(
                command,
                env=env,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                cwd=tmpdir,
            )
            stdout, stderr = process.communicate()
            if process.returncode != 0:
                print(f"Error running docker-compose {action}:")
                print(stderr.decode())
                raise typer.Exit(code=1)
            print(f"docker-compose {action} completed successfully.")

def get_temp_password():
    """Gets the temporary password from the container logs."""
    print("Getting temporary password from container logs...")
    for i in range(5):
        try:
            process = subprocess.Popen(
                ["docker", "logs", "qbittorrent-tormadeeasy"],
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
            )
            stdout, stderr = process.communicate()
            if process.returncode != 0:
                print(f"Error getting container logs: {stderr.decode()}")
                time.sleep(5)
                continue

            logs = stdout.decode("utf-8")
            if "temporary password" in logs:
                for line in logs.splitlines():
                    if "temporary password" in line:
                        password = line.split(":")[-1].strip()
                        print("Temporary password found.")
                        return password
        except Exception as e:
            print(f"Error getting logs: {e}")
        
        time.sleep(5)

    print("Could not find temporary password in container logs.")
    raise typer.Exit(code=1)

def add_torrent(qb_client: Client, magnet_link: str):
    """Adds a torrent to qBittorrent."""
    print("Adding torrent to qBittorrent...")
    try:
        qb_client.torrents_add(urls=magnet_link)
        print("Torrent added successfully.")
    except APIError as e:
        print(f"Failed to add torrent. APIError: {e}")
        raise typer.Exit(code=1)

def get_ngrok_public_url():
    """Gets the public URL from the ngrok API."""
    print("Getting ngrok public URL...")
    for i in range(5):
        try:
            response = requests.get("http://localhost:4040/api/tunnels")
            response.raise_for_status()
            tunnels = response.json()["tunnels"]
            for tunnel in tunnels:
                if tunnel["proto"] == "https":
                    print("ngrok public URL found.")
                    return tunnel["public_url"]
        except requests.exceptions.RequestException as e:
            print(f"Could not connect to ngrok API: {e}. Retrying...")
            time.sleep(5)
    return None

@app.command()
def down(magnet_link: str):
    """
    Downloads a torrent from a magnet link using a qBittorrent container.
    """
    ngrok_authtoken = get_ngrok_authtoken()
    current_working_dir = os.getcwd()
    download_dir_host = os.path.abspath(os.path.join(current_working_dir, "downloads"))
    config_dir_host = os.path.abspath(os.path.join(current_working_dir, "config"))
    os.makedirs(download_dir_host, exist_ok=True)
    os.makedirs(config_dir_host, exist_ok=True)

    manage_docker_compose("up", ngrok_authtoken, "", download_dir_host, config_dir_host)
    
    password = get_temp_password()
    
    qb_client = Client(host="localhost", port=8080, username="admin", password=password)
    try:
        qb_client.auth_log_in()
        print("Logged in to qBittorrent Web UI.")
    except APIError as e:
        print(f"Failed to log in to qBittorrent Web UI. APIError: {e}")
        raise typer.Exit(code=1)

    add_torrent(qb_client, magnet_link)
    print("Download started in the background. Use 'tormadeeasy progress' to check status.")

@app.command()
def progress():
    """
    Checks the progress of the running torrents.
    """
    password = get_temp_password()
    qb_client = Client(host="localhost", port=8080, username="admin", password=password)
    try:
        qb_client.auth_log_in()
    except APIError as e:
        print(f"Failed to log in to qBittorrent Web UI. APIError: {e}")
        raise typer.Exit(code=1)

    torrents = qb_client.torrents_info()
    if not torrents:
        print("No torrents are currently running.")
        return

    with Progress() as progress:
        for torrent in torrents:
            task = progress.add_task(f"[cyan]{torrent.name}", total=100)
            percent_complete = torrent.progress * 100
            progress.update(task, completed=percent_complete)
    
@app.command()
def up(file_path: str):
    """
    Creates a torrent for a local file and provides a magnet link.
    """
    if not os.path.exists(file_path):
        print(f"File not found: {file_path}")
        raise typer.Exit(code=1)

    ngrok_authtoken = get_ngrok_authtoken()
    
    current_working_dir = os.getcwd()
    upload_dir_host = os.path.abspath(os.path.join(current_working_dir, "uploads"))
    download_dir_host = os.path.abspath(os.path.join(current_working_dir, "downloads"))
    config_dir_host = os.path.abspath(os.path.join(current_working_dir, "config"))
    os.makedirs(upload_dir_host, exist_ok=True)
    os.makedirs(download_dir_host, exist_ok=True)
    os.makedirs(config_dir_host, exist_ok=True)

    file_name = os.path.basename(file_path)
    # The file path for qbittorrent-api needs to be relative to the mounted /uploads directory
    file_path_in_container = f"/uploads/{file_name}"
    
    print(f"Copying file to uploads directory: {os.path.join(upload_dir_host, file_name)}")
    shutil.copy(file_path, os.path.join(upload_dir_host, file_name))
    
    manage_docker_compose("up", ngrok_authtoken, upload_dir_host, download_dir_host, config_dir_host)
    
    public_url = get_ngrok_public_url()
    if not public_url:
        print("Could not get ngrok public URL. Please check the container logs.")
        raise typer.Exit(code=1)
        
    web_seed_url = f"{public_url}/{file_name}"
    
    trackers = [
        "udp://tracker.torrent.eu.org:451/announce",
        "udp://exodus.desync.com:6969/announce",
    ]
    
    password = get_temp_password()
    qb_client = Client(host="localhost", port=8080, username="admin", password=password)
    try:
        qb_client.auth_log_in()
        print("Logged in to qBittorrent Web UI for torrent creation.")
    except APIError as e:
        print(f"Failed to log in to qBittorrent Web UI. APIError: {e}")
        raise typer.Exit(code=1)

    print("Creating torrent using qBittorrent API...")
    try:
        create_torrent_response = qb_client.torrentcreator_add_task(
            save_path="/uploads",
            source_path=file_path_in_container,
            is_private=False,
            comment=f"Created by TorMadeEasy for {file_name}",
            piece_size=0,
            trackers="\n".join(trackers),
            web_seeds=web_seed_url,
            start_torrent=True,
        )
        print("Torrent creation job initiated via qBittorrent API.")
        
        torrent_hash = None
        for _ in range(10):
            torrents_info = qb_client.torrents_info()
            for t in torrents_info:
                if t.name == file_name:
                    torrent_hash = t.hash
                    break
            if torrent_hash:
                break
            time.sleep(2)
        
        if not torrent_hash:
            print("Could not find the newly created torrent in qBittorrent.")
            raise typer.Exit(code=1)
            
        magnet_link = f"magnet:?xt=urn:btih:{torrent_hash}&dn={urllib.parse.quote_plus(file_name)}"
        for tr_url in trackers:
            magnet_link += f"&tr={urllib.parse.quote_plus(tr_url)}"
        magnet_link += f"&ws={urllib.parse.quote_plus(web_seed_url)}"

        print("Magnet link created:")
        print(magnet_link)
        
    except APIError as e:
        print(f"Failed to create torrent via qBittorrent API. APIError: {e}")
        raise typer.Exit(code=1)

    print("Your file is now seeding. Run 'tormadeeasy stop' to stop.")

@app.command()
def stop():
    """
    Stops the tormadeeasy services.
    """
    ngrok_authtoken = get_ngrok_authtoken()
    current_working_dir = os.getcwd()
    upload_dir_host = os.path.abspath(os.path.join(current_working_dir, "uploads"))
    download_dir_host = os.path.abspath(os.path.join(current_working_dir, "downloads"))
    config_dir_host = os.path.abspath(os.path.join(current_working_dir, "config"))

    manage_docker_compose("down", ngrok_authtoken, upload_dir_host, download_dir_host, config_dir_host)


if __name__ == "__main__":
    app()