import os
import subprocess
import shutil
import urllib.request
import tarfile
import re
from pathlib import Path
from rich.panel import Panel
from rich.prompt import Prompt, Confirm
from rich.progress import Progress
from rich.table import Table
from .utils import console

# using 0.8.1
SERVER_URL = "https://github.com/TES3MP/TES3MP/releases/download/tes3mp-0.8.1/tes3mp-server-GNU+Linux-x86_64-release-0.8.1-68954091c5-6da3fdea59.tar.gz"
SERVER_DIR = Path.home() / "Games" / "TES3MP_Server"
SERVER_PORT = 25565

def get_server_root():
    """Find the server installation folder."""
    if not SERVER_DIR.exists():
        return None
    for pattern in ["TES3MP-Server*", "TES3MP-server*", "tes3mp-server*"]:
        server_root = next(SERVER_DIR.glob(pattern), None)
        if server_root:
            return server_root
    return None

def get_tailscale_ip():
    """Get the user's Tailscale IP address."""
    try:
        result = subprocess.run(["tailscale", "ip", "-4"], capture_output=True, text=True)
        if result.returncode == 0:
            return result.stdout.strip()
    except Exception:
        pass
    return None

def is_tailscale_running():
    """Check if Tailscale daemon is running."""
    try:
        result = subprocess.run(["tailscale", "status"], capture_output=True, text=True)
        return result.returncode == 0
    except Exception:
        return False


def install_tailscale():
    """Offer to install Tailscale if not present."""
    console.print(Panel("[bold blue]Install Tailscale[/bold blue]", expand=False))
    console.print("[yellow]Tailscale provides secure private networking for your server.[/yellow]")
    console.print("[dim]This is optional - you can use public IP instead.[/dim]\n")
    
    if not Confirm.ask("Install Tailscale now?"):
        return False
    
    console.print("[cyan][*] Installing Tailscale...[/cyan]")
    try:
        # Use the official install script
        subprocess.run(
            "curl -fsSL https://tailscale.com/install.sh | sh",
            shell=True, check=True
        )
        console.print("[green][+] Tailscale installed![/green]")
        console.print("[yellow]Run 'sudo tailscale up' to connect.[/yellow]")
        return True
    except subprocess.CalledProcessError as e:
        console.print(f"[red][!] Installation failed: {e}[/red]")
        console.print("[dim]Try manually: curl -fsSL https://tailscale.com/install.sh | sh[/dim]")
        return False


def start_tailscale():
    """Start Tailscale if it's not running."""
    if is_tailscale_running():
        return True
    
    # Check if Tailscale is even installed
    if not shutil.which("tailscale"):
        console.print("[yellow][*] Tailscale is not installed.[/yellow]")
        if Confirm.ask("Install Tailscale now?"):
            install_tailscale()
        return False
        
    console.print("[yellow][*] Tailscale is not running.[/yellow]")
    
    if Confirm.ask("Start Tailscale now?"):
        import time
        
        # Check if systemd is available
        has_systemd = False
        try:
            result = subprocess.run(["systemctl", "is-system-running"], capture_output=True, text=True)
            has_systemd = result.returncode == 0 or "running" in result.stdout
        except Exception:
            pass
        
        try:
            if has_systemd:
                # Systemd is available - use systemctl
                console.print("[cyan][*] Starting Tailscale via systemd...[/cyan]")
                subprocess.run(["sudo", "systemctl", "start", "tailscaled"], check=True)
                subprocess.run(["sudo", "tailscale", "up"], check=True)
                console.print("[green][+] Tailscale started![/green]")
                return True
            else:
                # Non-systemd environment (Docker, Cloud Shell, Colab, etc.)
                console.print("[yellow][*] Systemd not available. Starting tailscaled directly...[/yellow]")
                
                # Check if tailscaled is already running
                try:
                    subprocess.run(["pgrep", "-x", "tailscaled"], check=True, capture_output=True)
                    console.print("[dim]tailscaled already running[/dim]")
                except subprocess.CalledProcessError:
                    # Start tailscaled in background with userspace networking (for containers)
                    console.print("[cyan][*] Starting tailscaled daemon...[/cyan]")
                    
                    # Use state directory that's writable
                    state_dir = Path.home() / ".tailscale"
                    state_dir.mkdir(exist_ok=True)
                    
                    # Start tailscaled with userspace networking for restricted environments
                    # nohup ensures it survives terminal close
                    cmd = f"nohup sudo tailscaled --state={state_dir}/tailscaled.state --socket={state_dir}/tailscaled.sock --tun=userspace-networking > {state_dir}/tailscaled.log 2>&1 &"
                    subprocess.run(cmd, shell=True)
                    
                    console.print("[dim]Waiting for tailscaled to start...[/dim]")
                    time.sleep(3)
                
                # Now bring up tailscale
                console.print("[cyan][*] Connecting to Tailscale network...[/cyan]")
                socket_path = Path.home() / ".tailscale" / "tailscaled.sock"
                
                if socket_path.exists():
                    subprocess.run(["sudo", "tailscale", "--socket", str(socket_path), "up"])
                else:
                    subprocess.run(["sudo", "tailscale", "up"])
                
                console.print("[green][+] Tailscale started![/green]")
                return True
                
        except subprocess.CalledProcessError as e:
            console.print(f"[red][!] Failed to start Tailscale: {e}[/red]")
            console.print("[dim]For non-systemd systems, try manually:[/dim]")
            console.print("[dim]  sudo tailscaled --tun=userspace-networking &[/dim]")
            console.print("[dim]  sudo tailscale up[/dim]")
            return False
    return False


def check_server_dependencies(server_root=None):
    """
    Check and install required dependencies for the server.
    Uses ldd to find ALL missing shared libraries.
    """
    console.print("[cyan][*] Checking server dependencies...[/cyan]")
    
    # If we have the server installed, use ldd on the actual binary
    if server_root is None:
        server_root = get_server_root()
    
    missing_libs = []
    
    if server_root:
        server_bin = server_root / "tes3mp-server.x86_64"
        if server_bin.exists():
            try:
                # Use ldd to find missing libraries
                # Set LD_LIBRARY_PATH to include bundled libs
                lib_dir = server_root / "lib"
                env = os.environ.copy()
                if lib_dir.exists():
                    existing_ld = env.get("LD_LIBRARY_PATH", "")
                    env["LD_LIBRARY_PATH"] = f"{lib_dir}:{existing_ld}" if existing_ld else str(lib_dir)
                
                result = subprocess.run(["ldd", str(server_bin)], capture_output=True, text=True, env=env)
                
                for line in result.stdout.splitlines():
                    if "not found" in line:
                        lib_name = line.split("=>")[0].strip()
                        missing_libs.append(lib_name)
                        
            except Exception as e:
                console.print(f"[dim]Could not run ldd: {e}[/dim]")
    
    # Fallback: Check for commonly missing libraries via ldconfig
    if not missing_libs:
        try:
            result = subprocess.run(["ldconfig", "-p"], capture_output=True, text=True)
            ldconfig_output = result.stdout
            
            # List of libraries the server commonly needs
            common_libs = ["libluajit", "libboost_system", "libboost_filesystem", 
                           "libboost_program_options", "libboost_iostreams"]
            
            for lib in common_libs:
                if lib not in ldconfig_output:
                    missing_libs.append(lib)
        except Exception:
            pass
    
    if not missing_libs:
        console.print("[green]✓ All dependencies satisfied[/green]")
        return True
    
    console.print(f"[yellow][!] Missing libraries: {', '.join(missing_libs)}[/yellow]")
    
    # Map library names to package names for common distros
    # Format: {lib_substring: {apt, yum/dnf, pacman, apk}}
    LIB_TO_PKG = {
        "libluajit": {"apt": "libluajit-5.1-2", "yum": "luajit", "pacman": "luajit", "apk": "luajit"},
        "libboost_system": {"apt": "libboost-system1.74.0", "yum": "boost-system", "pacman": "boost-libs", "apk": "boost-system"},
        "libboost_filesystem": {"apt": "libboost-filesystem1.74.0", "yum": "boost-filesystem", "pacman": "boost-libs", "apk": "boost-filesystem"},
        "libboost_program_options": {"apt": "libboost-program-options1.74.0", "yum": "boost-program-options", "pacman": "boost-libs", "apk": "boost"},
        "libboost_iostreams": {"apt": "libboost-iostreams1.74.0", "yum": "boost-iostreams", "pacman": "boost-libs", "apk": "boost-iostreams"},
    }
    
    # Determine package manager
    pkg_manager = None
    pkg_cmd = None
    if shutil.which("apt-get"):
        pkg_manager = "apt"
        pkg_cmd = ["sudo", "apt-get", "install", "-y"]
    elif shutil.which("dnf"):
        pkg_manager = "yum"  # Same packages as yum
        pkg_cmd = ["sudo", "dnf", "install", "-y"]
    elif shutil.which("yum"):
        pkg_manager = "yum"
        pkg_cmd = ["sudo", "yum", "install", "-y"]
    elif shutil.which("pacman"):
        pkg_manager = "pacman"
        pkg_cmd = ["sudo", "pacman", "-S", "--noconfirm"]
    elif shutil.which("apk"):
        pkg_manager = "apk"
        pkg_cmd = ["sudo", "apk", "add"]
    
    if not pkg_manager:
        console.print("[red][!] Unknown package manager. Please install these libraries manually:[/red]")
        for lib in missing_libs:
            console.print(f"  - {lib}")
        return False
    
    # Build package list
    packages_to_install = set()
    for lib in missing_libs:
        for lib_key, pkg_map in LIB_TO_PKG.items():
            if lib_key in lib:
                if pkg_manager in pkg_map:
                    packages_to_install.add(pkg_map[pkg_manager])
                break
    
    if not packages_to_install:
        console.print("[yellow]Could not map all libraries to packages. You may need to install manually.[/yellow]")
        return False
    
    console.print(f"[cyan]Packages to install: {', '.join(packages_to_install)}[/cyan]")
    
    if not Confirm.ask("Install missing dependencies now?"):
        return False
    
    try:
        # Update package cache for apt
        if pkg_manager == "apt":
            console.print("[cyan][*] Updating package cache...[/cyan]")
            subprocess.run(["sudo", "apt-get", "update"], check=False)
        
        # Install packages
        cmd = pkg_cmd + list(packages_to_install)
        console.print(f"[cyan][*] Running: {' '.join(cmd)}[/cyan]")
        subprocess.run(cmd, check=True)
        
        console.print("[green][+] Dependencies installed![/green]")
        return True
        
    except subprocess.CalledProcessError as e:
        console.print(f"[red][!] Installation failed: {e}[/red]")
        return False


def install_server():
    """Download and extract the server binaries."""
    if SERVER_DIR.exists():
        console.print(f"[yellow][*] Server already installed at {SERVER_DIR}[/yellow]")
        server_root = get_server_root()
        # Check dependencies on existing install
        check_server_dependencies(server_root)
        return server_root
        
    if not Confirm.ask(f"Install server to {SERVER_DIR}?"):
        return None
        
    SERVER_DIR.mkdir(parents=True, exist_ok=True)
    tar_path = SERVER_DIR / "server.tar.gz"
    
    console.print("[cyan][*] Downloading Server binaries...[/cyan]")
    
    with Progress() as progress:
        task = progress.add_task("Downloading...", total=None)
        try:
            urllib.request.urlretrieve(SERVER_URL, tar_path)
            progress.update(task, completed=100, total=100)
        except Exception as e:
            console.print(f"[bold red][!] Download failed: {e}[/bold red]")
            return None
    
    console.print("[*] Extracting...")
    with tarfile.open(tar_path) as tar:
        tar.extractall(path=SERVER_DIR)
    os.remove(tar_path)
    
    console.print("[green][+] Server installed![/green]")
    
    # Check dependencies AFTER installing (so ldd can work on the actual binary)
    server_root = get_server_root()
    if server_root:
        check_server_dependencies(server_root)
    
    return server_root

def configure_server(server_root):
    """Configure server name and password."""
    cfg_path = server_root / "tes3mp-server-default.cfg"
    if not cfg_path.exists():
        console.print("[bold red][!] Config file missing.[/bold red]")
        return False

    name = Prompt.ask("Enter Server Name", default="My TES3MP Server")
    password = Prompt.ask("Enter Password (leave empty for public)", default="")

    with open(cfg_path, 'r') as f:
        cfg = f.read()

    cfg = re.sub(r'hostname = ".*?"', f'hostname = "{name}"', cfg)
    cfg = re.sub(r'password = ".*?"', f'password = "{password}"', cfg)

    with open(cfg_path, 'w') as f:
        f.write(cfg)
        
    console.print(f"[green][+] Server configured: {name}[/green]")
    return True

def show_connection_info():
    """Display connection information for friends."""
    # Try to start Tailscale if not running
    if not is_tailscale_running():
        start_tailscale()
    
    ts_ip = get_tailscale_ip()
    
    console.print(Panel("[bold cyan]How to Connect[/bold cyan]", expand=False))
    
    table = Table(show_header=False, box=None)
    table.add_column("Label", style="bold")
    table.add_column("Value", style="green")
    
    if ts_ip:
        table.add_row("Tailscale IP:", ts_ip)
        table.add_row("Port:", str(SERVER_PORT))
        table.add_row("Full Address:", f"{ts_ip}:{SERVER_PORT}")
    else:
        table.add_row("Status:", "[red]Tailscale not running[/red]")
        
    console.print(table)
    console.print()
    
    if ts_ip:
        console.print("[bold]Share this with your friends:[/bold]")
        console.print(f"  [cyan]{ts_ip}:{SERVER_PORT}[/cyan]")
        console.print()
        console.print("[dim]Friends need:[/dim]")
        console.print("  1. TES3MP installed (pip install tes3mp-easy)")
        console.print("  2. Tailscale connected to your network")
        console.print("  3. Same Morrowind game files as you")

def start_server(server_root):
    """Start the server process."""
    server_bin = server_root / "tes3mp-server"
    
    if not server_bin.exists():
        console.print("[bold red][!] Server binary not found.[/bold red]")
        return
    
    # Show connection info first
    show_connection_info()
    
    console.print()
    console.print("[bold green]Starting server...[/bold green]")
    console.print("[dim]Press Ctrl+C to stop the server[/dim]")
    console.print()
    
    try:
        # Run server in foreground
        subprocess.run([str(server_bin)], cwd=str(server_root))
    except KeyboardInterrupt:
        console.print("\n[yellow]Server stopped.[/yellow]")


def configure_server_data():
    """Configure the path to ESM files for the server."""
    from .utils import save_data_path, load_stored_data_path
    
    console.print(Panel("[bold blue]Configure ESM Files[/bold blue]", expand=False))
    console.print("[yellow]The server needs the ESM files to set checksum requirements.[/yellow]")
    console.print("[dim]Only the .esm files are required (~95MB), not the full game.[/dim]\n")
    
    current_path = load_stored_data_path()
    if current_path and current_path.exists():
        console.print(f"Current path: [cyan]{current_path}[/cyan]")
        if not Confirm.ask("Do you want to change it?"):
            return current_path
    
    console.print("\nEnter the path to folder containing Morrowind.esm, Tribunal.esm, Bloodmoon.esm")
    
    while True:
        path_str = Prompt.ask("ESM Files Path")
        data_path = Path(path_str).expanduser().resolve()
        
        if (data_path / "Morrowind.esm").exists():
            save_data_path(data_path)
            console.print(f"[green]✅ ESM path saved: {data_path}[/green]")
            
            # Check which ESM files are present
            for esm in ["Morrowind.esm", "Tribunal.esm", "Bloodmoon.esm"]:
                if (data_path / esm).exists():
                    console.print(f"  [green]✓[/green] {esm}")
                else:
                    console.print(f"  [red]✗[/red] {esm} [dim](optional)[/dim]")
            
            return data_path
        else:
            console.print("[bold red][!] Morrowind.esm not found in that folder.[/bold red]")
            if not Confirm.ask("Try again?"):
                return None


def get_public_ip():
    """Get the server's public IP address."""
    try:
        import urllib.request
        return urllib.request.urlopen('https://api.ipify.org', timeout=5).read().decode('utf8')
    except Exception:
        return None


def tailscale_invite():
    """Generate Tailscale invite instructions for friends."""
    console.print(Panel("[bold blue]Invite Friends via Tailscale[/bold blue]", expand=False))
    
    # Check if Tailscale is running and get our IP
    ts_ip = get_tailscale_ip()
    if not ts_ip:
        console.print("[yellow]Tailscale is not running. Start it first![/yellow]")
        if Confirm.ask("Start Tailscale now?"):
            start_tailscale()
            ts_ip = get_tailscale_ip()
    
    if not ts_ip:
        console.print("[red]Could not get Tailscale IP. Please start Tailscale first.[/red]")
        return
    
    # Get machine info
    try:
        result = subprocess.run(["tailscale", "status", "--json"], capture_output=True, text=True)
        import json
        status = json.loads(result.stdout)
        machine_name = status.get("Self", {}).get("HostName", "your-server")
        tailnet = status.get("MagicDNSSuffix", "")
    except Exception:
        machine_name = "your-server"
        tailnet = ""
    
    console.print(f"\n[green]Your server's Tailscale IP:[/green] [bold cyan]{ts_ip}[/bold cyan]")
    console.print(f"[green]Server name:[/green] [cyan]{machine_name}[/cyan]")
    if tailnet:
        console.print(f"[green]Tailnet:[/green] [cyan]{tailnet}[/cyan]")
    
    console.print("\n" + "="*60)
    console.print("[bold yellow]Option 1: Invite via Tailscale Admin Console (Recommended)[/bold yellow]")
    console.print("="*60)
    console.print("""
1. Go to: [link]https://login.tailscale.com/admin/users[/link]
2. Click \"Invite users\"
3. Enter your friend's email address
4. They'll get an email with instructions to join your network
""")
    
    console.print("="*60)
    console.print("[bold yellow]Option 2: Share Node (Quick Access)[/bold yellow]")
    console.print("="*60)
    
    # Try to enable node sharing
    try:
        # Check if funnel/share is available
        result = subprocess.run(["tailscale", "share", "--help"], capture_output=True, text=True)
        if result.returncode == 0:
            console.print("""
You can share this specific server with external users:
""")
            console.print(f"[cyan]tailscale share {machine_name}[/cyan]")
            console.print("\nThis allows users outside your Tailnet to connect temporarily.")
    except Exception:
        pass
    
    console.print("\n" + "="*60)
    console.print("[bold yellow]Option 3: Give Friends This Command[/bold yellow]")
    console.print("="*60)
    console.print("""
[bold]Your friends should run these commands:[/bold]
""")
    console.print("[cyan]# 1. Install Tailscale[/cyan]")
    console.print("curl -fsSL https://tailscale.com/install.sh | sh")
    console.print()
    console.print("[cyan]# 2. Connect to Tailscale (they'll need to be in your network)[/cyan]")
    console.print("sudo tailscale up")
    console.print()
    console.print("[cyan]# 3. Launch TES3MP and connect to:[/cyan]")
    console.print(f"[bold green]{ts_ip}:{SERVER_PORT}[/bold green]")
    
    console.print("\n" + "="*60)
    console.print("[bold yellow]One-Liner for Friends:[/bold yellow]")
    console.print("="*60)
    oneliner = f"""
pip install tes3mp-easy && curl -fsSL https://tailscale.com/install.sh | sh && sudo tailscale up
# Then connect to: {ts_ip}:{SERVER_PORT}
"""
    console.print(f"[dim]{oneliner}[/dim]")


def setup_server():
    """Main server menu."""
    console.print(Panel("[bold blue]Host a Server[/bold blue]", expand=False))
    
    # Ensure server is installed
    server_root = get_server_root()
    if not server_root:
        server_root = install_server()
        if not server_root:
            return
    
    while True:
        console.print()
        console.print("[bold]Server Menu:[/bold]")
        console.print("1. [green]Start Server[/green]")
        console.print("2. Show Connection Info")
        console.print("3. Configure (Name/Password)")
        console.print("4. Setup ESM Files")
        console.print("5. [magenta]Invite Friends (Tailscale)[/magenta]")
        console.print("6. Back to Main Menu")
        
        choice = Prompt.ask("Select", choices=["1", "2", "3", "4", "5", "6"])
        
        if choice == "1":
            start_server(server_root)
        elif choice == "2":
            show_connection_info()
            # Also show public IP
            public_ip = get_public_ip()
            if public_ip:
                console.print(f"\n[bold]Public IP:[/bold] [green]{public_ip}:{SERVER_PORT}[/green]")
                console.print("[dim]Use this if hosting on a VPS/cloud server[/dim]")
            Prompt.ask("\nPress Enter to continue")
        elif choice == "3":
            configure_server(server_root)
            Prompt.ask("\nPress Enter to continue")
        elif choice == "4":
            configure_server_data()
            Prompt.ask("\nPress Enter to continue")
        elif choice == "5":
            tailscale_invite()
            Prompt.ask("\nPress Enter to continue")
        elif choice == "6":
            break

