"""Console commands - Web UI for managing local Paracord projects."""

import subprocess
import sys
from pathlib import Path
from typing import Annotated

import typer
from rich.table import Table

from paracord.utils.console import (
    console,
    print_header,
    print_success,
    print_error,
    print_warning,
    print_info,
)
from paracord.core.project_registry import (
    register_project,
    list_projects,
    remove_project,
    get_project,
    update_project,
    is_project_registered,
)
from paracord.console.scanner import get_port_status
from paracord.console.tunnel import (
    generate_tunnel_config,
    get_tunnel_instructions,
    validate_subdomain,
)


# Console environment paths
CONSOLE_DIR = Path.home() / ".paracord" / "console"
CONSOLE_VENV = CONSOLE_DIR / ".venv"
CONSOLE_PYTHON = CONSOLE_VENV / "bin" / "python"
CONSOLE_PID_FILE = CONSOLE_DIR / "paracord-console.pid"
CONSOLE_LOG_FILE = CONSOLE_DIR / "paracord-console.log"
CONSOLE_PORT = 7272
# Console needs fastapi/jinja2/uvicorn for web server, plus pyyaml/keyring for paracord modules
CONSOLE_DEPS = ["fastapi", "jinja2", "uvicorn", "pyyaml", "keyring"]


console_app = typer.Typer(
    name="console",
    help="Web console for managing local Paracord projects.",
    invoke_without_command=True,
)

tunnel_app = typer.Typer(
    name="tunnel",
    help="Cloudflare tunnel configuration commands.",
)

console_app.add_typer(tunnel_app, name="tunnel")


def get_console_python() -> Path:
    """Get path to console venv Python executable."""
    if sys.platform == "win32":
        return CONSOLE_VENV / "Scripts" / "python.exe"
    return CONSOLE_VENV / "bin" / "python"


def is_console_installed() -> bool:
    """Check if console environment is set up."""
    python_path = get_console_python()
    if not python_path.exists():
        return False

    # Verify deps are installed
    result = subprocess.run(
        [str(python_path), "-c", "import fastapi, jinja2, uvicorn"],
        capture_output=True,
    )
    return result.returncode == 0


def install_console() -> bool:
    """Install console environment at ~/.paracord/console/.

    Creates a dedicated virtual environment and installs dependencies.

    Returns:
        True if installation succeeded, False otherwise
    """
    console.print(f"\n[info]Setting up console environment at {CONSOLE_DIR}[/info]")

    # Create console directory
    CONSOLE_DIR.mkdir(parents=True, exist_ok=True)

    # Create virtual environment using uv (preferred) or venv
    python_path = get_console_python()

    if not CONSOLE_VENV.exists():
        console.print("[dim]Creating virtual environment...[/dim]")

        try:
            # Try uv first
            result = subprocess.run(
                ["uv", "venv", str(CONSOLE_VENV)],
                capture_output=True,
                text=True,
            )
            if result.returncode != 0:
                raise FileNotFoundError("uv failed")
        except FileNotFoundError:
            # Fall back to built-in venv
            import venv

            venv.create(CONSOLE_VENV, with_pip=True)

    if not python_path.exists():
        print_error(f"Failed to create virtual environment at {CONSOLE_VENV}")
        return False

    # Install dependencies
    console.print("[dim]Installing dependencies (fastapi, jinja2, uvicorn)...[/dim]")

    try:
        # Try uv pip first
        result = subprocess.run(
            ["uv", "pip", "install", "--python", str(python_path)] + CONSOLE_DEPS,
            capture_output=True,
            text=True,
        )
        if result.returncode != 0:
            raise FileNotFoundError("uv pip failed")
    except FileNotFoundError:
        # Fall back to pip
        result = subprocess.run(
            [str(python_path), "-m", "pip", "install"] + CONSOLE_DEPS,
            capture_output=True,
            text=True,
        )

    if result.returncode != 0:
        print_error("Failed to install dependencies.")
        console.print(f"[dim]{result.stderr}[/dim]")
        return False

    print_success(f"Console installed at {CONSOLE_DIR}")
    return True


def prompt_install_console() -> bool:
    """Prompt user to install console environment.

    Returns:
        True if installed, False if user declined
    """
    print_warning("Console environment not found.")
    console.print(f"\nThis will create an isolated environment at:")
    console.print(f"  [path]{CONSOLE_DIR}[/path]")
    console.print(f"\nWith packages: {', '.join(CONSOLE_DEPS)}")
    console.print()

    install = typer.confirm("Install console now?", default=True)

    if not install:
        return False

    return install_console()


def get_paracord_src_path() -> Path:
    """Get the path to the paracord source package."""
    # This file is at src/paracord/commands/console.py
    # We need src/paracord (or the installed package location)
    return Path(__file__).parent.parent.parent


def run_console_server(host: str = "127.0.0.1", port: int = CONSOLE_PORT, open_browser: bool = True):
    """Run the console server using the isolated console environment (foreground)."""
    import webbrowser
    import threading

    python_path = get_console_python()
    paracord_path = get_paracord_src_path()

    # Open browser from main process (more reliable than from subprocess)
    if open_browser:
        def open_browser_delayed():
            import time
            time.sleep(1.0)
            webbrowser.open(f"http://{host}:{port}")

        threading.Thread(target=open_browser_delayed, daemon=True).start()

    # Build the server script to run (without browser opening)
    server_script = f'''
import sys
sys.path.insert(0, {repr(str(paracord_path))})

from paracord.console.server import run_server
run_server(host={repr(host)}, port={port}, open_browser=False)
'''

    # Run using console venv's Python
    subprocess.run([str(python_path), "-c", server_script])


def get_console_pid() -> int | None:
    """Get the PID of the running console daemon."""
    if not CONSOLE_PID_FILE.exists():
        return None

    try:
        pid = int(CONSOLE_PID_FILE.read_text().strip())
        # Check if process is actually running
        import os
        os.kill(pid, 0)  # Signal 0 checks if process exists
        return pid
    except (ValueError, ProcessLookupError, PermissionError):
        # PID file exists but process is not running
        CONSOLE_PID_FILE.unlink(missing_ok=True)
        return None


def is_console_running() -> bool:
    """Check if the console daemon is running."""
    return get_console_pid() is not None


def start_console_daemon(host: str = "127.0.0.1", port: int = CONSOLE_PORT) -> bool:
    """Start the console server as a background daemon.

    Returns:
        True if started successfully, False otherwise
    """
    import os

    if is_console_running():
        return False  # Already running

    python_path = get_console_python()
    paracord_path = get_paracord_src_path()

    # Ensure console directory exists
    CONSOLE_DIR.mkdir(parents=True, exist_ok=True)

    # Build the server script
    server_script = f'''
import sys
sys.path.insert(0, {repr(str(paracord_path))})

from paracord.console.server import run_server
run_server(host={repr(host)}, port={port}, open_browser=False)
'''

    # Open log file for output
    log_file = open(CONSOLE_LOG_FILE, "w")

    # Start the server in background
    process = subprocess.Popen(
        [str(python_path), "-c", server_script],
        stdout=log_file,
        stderr=log_file,
        start_new_session=True,  # Detach from parent process
    )

    # Write PID file
    CONSOLE_PID_FILE.write_text(str(process.pid))

    return True


def stop_console_daemon() -> bool:
    """Stop the console daemon.

    Returns:
        True if stopped successfully, False if not running
    """
    import os
    import signal

    pid = get_console_pid()
    if pid is None:
        return False  # Not running

    try:
        # Send SIGTERM for graceful shutdown
        os.kill(pid, signal.SIGTERM)

        # Wait briefly for process to terminate
        import time
        for _ in range(10):  # Wait up to 1 second
            time.sleep(0.1)
            try:
                os.kill(pid, 0)
            except ProcessLookupError:
                break  # Process terminated
        else:
            # Process didn't terminate, force kill
            os.kill(pid, signal.SIGKILL)

    except ProcessLookupError:
        pass  # Process already terminated
    except PermissionError:
        return False  # Can't kill the process

    # Clean up PID file
    CONSOLE_PID_FILE.unlink(missing_ok=True)

    return True


def detect_project_info(path: Path) -> tuple[str, int | None, str | None]:
    """Detect project name, port, and template source from project files.

    Returns:
        Tuple of (name, port, template_source)
    """
    name = path.name

    # Try to detect port from common config files
    port = None
    template_source = None

    # Check rav.yaml for port configuration
    rav_file = path / "rav.yaml"
    if rav_file.exists():
        try:
            import yaml

            with open(rav_file) as f:
                rav_config = yaml.safe_load(f)

            # Look for common port patterns
            scripts = rav_config.get("scripts", {})
            for script_name, script_config in scripts.items():
                if isinstance(script_config, dict):
                    cmd = script_config.get("cmd", "")
                    # Look for --port or -p flags
                    if "--port" in cmd or "-p " in cmd:
                        import re

                        match = re.search(r"(?:--port|-p)\s*(\d+)", cmd)
                        if match:
                            port = int(match.group(1))
                            break

        except Exception:
            pass

    # Check .copier-answers.yml for template source
    copier_file = path / ".copier-answers.yml"
    if copier_file.exists():
        try:
            import yaml

            with open(copier_file) as f:
                copier_config = yaml.safe_load(f)

            template_source = copier_config.get("_src_path")

        except Exception:
            pass

    return name, port, template_source


@console_app.callback(invoke_without_command=True)
def console_default(ctx: typer.Context) -> None:
    """Open the web console in browser.

    If no subcommand is provided, starts the server in background and opens the browser.
    """
    if ctx.invoked_subcommand is not None:
        return

    # Check if console is installed
    if not is_console_installed():
        if not prompt_install_console():
            raise typer.Exit(1)

    # If already running, just open browser
    if is_console_running():
        pid = get_console_pid()
        print_info(f"Console already running (PID: {pid})")
        console.print(f"  Opening http://127.0.0.1:{CONSOLE_PORT}")
        import webbrowser
        webbrowser.open(f"http://127.0.0.1:{CONSOLE_PORT}")
        return

    # Start server in background
    if start_console_daemon():
        import time
        time.sleep(0.5)

        pid = get_console_pid()
        print_success(f"Console started (PID: {pid})")
        console.print(f"  URL: http://127.0.0.1:{CONSOLE_PORT}")

        # Open browser
        import webbrowser
        time.sleep(0.5)
        webbrowser.open(f"http://127.0.0.1:{CONSOLE_PORT}")
    else:
        print_error("Failed to start console")
        raise typer.Exit(1)


@console_app.command(name="add")
def console_add(
    port: Annotated[
        int | None,
        typer.Option("--port", "-p", help="Port the project runs on"),
    ] = None,
    name: Annotated[
        str | None,
        typer.Option("--name", "-n", help="Display name for the project"),
    ] = None,
    subdomain: Annotated[
        str | None,
        typer.Option("--subdomain", "-s", help="Cloudflare tunnel subdomain"),
    ] = None,
) -> None:
    """Add the current project to the console."""
    cwd = Path.cwd()

    # Check if already registered
    if is_project_registered(cwd):
        project = get_project(cwd)
        print_warning(f"Project already registered: {project.get('name', cwd.name)}")

        update = typer.confirm("Update the registration?", default=True)
        if not update:
            raise typer.Exit(0)

    # Detect project info
    detected_name, detected_port, template_source = detect_project_info(cwd)

    # Use provided values or detected/default values
    project_name = name or detected_name
    project_port = port or detected_port

    # Prompt for port if not provided or detected
    if project_port is None:
        project_port = typer.prompt("Port", type=int, default=8000)

    # Prompt for subdomain if not provided
    if subdomain is None:
        subdomain = typer.prompt(
            "Cloudflare subdomain (leave empty to skip)",
            default="",
            show_default=False,
        )

    # Validate subdomain if provided
    if subdomain:
        is_valid, error = validate_subdomain(subdomain)
        if not is_valid:
            print_error(f"Invalid subdomain: {error}")
            raise typer.Exit(1)

    # Register/update project
    if is_project_registered(cwd):
        update_project(
            cwd,
            name=project_name,
            port=project_port,
            cloudflare_subdomain=subdomain if subdomain else None,
        )
        print_success(f"Updated project: {project_name}")
    else:
        register_project(
            cwd,
            name=project_name,
            port=project_port,
            template_source=template_source,
            cloudflare_subdomain=subdomain if subdomain else None,
        )
        print_success(f"Registered project: {project_name}")

    console.print(f"  Path: {cwd}")
    console.print(f"  Port: {project_port}")
    if subdomain:
        console.print(f"  Subdomain: {subdomain}")

    console.print("\n[dim]Run [command]paracord console[/command] to open the web console.[/dim]")


@console_app.command(name="install")
def console_install_cmd() -> None:
    """Install or reinstall the console environment."""
    if is_console_installed():
        reinstall = typer.confirm(
            f"Console already installed at {CONSOLE_DIR}. Reinstall?",
            default=False,
        )
        if not reinstall:
            print_info("Cancelled.")
            return

        # Remove existing venv
        import shutil

        if CONSOLE_VENV.exists():
            shutil.rmtree(CONSOLE_VENV)

    if install_console():
        print_success("Console environment ready.")
    else:
        print_error("Failed to install console environment.")
        raise typer.Exit(1)


@console_app.command(name="list")
def console_list() -> None:
    """List all registered projects."""
    projects = list_projects()

    print_header("Registered Projects")

    if not projects:
        console.print("\n[warning]No projects registered.[/warning]")
        console.print("  Run [command]paracord console add[/command] in a project directory to register it.")
        return

    console.print()

    table = Table(border_style="dim")
    table.add_column("Name", style="bold")
    table.add_column("Port", justify="right")
    table.add_column("Status", justify="center")
    table.add_column("Path")

    for path, project in projects.items():
        project_name = project.get("name", Path(path).name)
        port = project.get("port", 8000)
        status = get_port_status(port)

        status_text = "[green]Running[/green]" if status.is_listening else "[dim]Stopped[/dim]"

        # Shorten path if it's in home directory
        display_path = path
        home = str(Path.home())
        if display_path.startswith(home):
            display_path = "~" + display_path[len(home):]

        table.add_row(project_name, str(port), status_text, display_path)

    console.print(table)
    console.print(f"\n[dim]{len(projects)} project(s) registered[/dim]")


@console_app.command(name="status")
def console_status() -> None:
    """Show running status of all projects."""
    projects = list_projects()

    print_header("Project Status")

    if not projects:
        console.print("\n[warning]No projects registered.[/warning]")
        return

    console.print()

    table = Table(border_style="dim")
    table.add_column("Name", style="bold")
    table.add_column("Port", justify="right")
    table.add_column("Status", justify="center")
    table.add_column("Process")
    table.add_column("PID", justify="right")

    running_count = 0
    for path, project in projects.items():
        project_name = project.get("name", Path(path).name)
        port = project.get("port", 8000)
        status = get_port_status(port)

        if status.is_listening:
            running_count += 1
            status_text = "[green]Running[/green]"
            process = status.process_name or "-"
            pid = str(status.pid) if status.pid else "-"
        else:
            status_text = "[dim]Stopped[/dim]"
            process = "-"
            pid = "-"

        table.add_row(project_name, str(port), status_text, process, pid)

    console.print(table)
    console.print(f"\n[dim]{running_count}/{len(projects)} project(s) running[/dim]")


@console_app.command(name="serve", hidden=True)
def console_serve(
    host: Annotated[
        str,
        typer.Option("--host", "-h", help="Host to bind to"),
    ] = "127.0.0.1",
    port: Annotated[
        int,
        typer.Option("--port", "-p", help="Port to listen on"),
    ] = CONSOLE_PORT,
) -> None:
    """Start the console server in foreground (blocks terminal)."""
    # Check if console is installed
    if not is_console_installed():
        if not prompt_install_console():
            raise typer.Exit(1)

    print_info(f"Starting Paracord Console on http://{host}:{port}")
    run_console_server(host=host, port=port, open_browser=False)


@console_app.command(name="start")
def console_start(
    host: Annotated[
        str,
        typer.Option("--host", "-h", help="Host to bind to"),
    ] = "127.0.0.1",
    port: Annotated[
        int,
        typer.Option("--port", "-p", help="Port to listen on"),
    ] = CONSOLE_PORT,
    open_browser: Annotated[
        bool,
        typer.Option("--open/--no-open", help="Open browser after starting"),
    ] = True,
) -> None:
    """Start the console server in background."""
    # Check if console is installed
    if not is_console_installed():
        if not prompt_install_console():
            raise typer.Exit(1)

    if is_console_running():
        pid = get_console_pid()
        print_warning(f"Console is already running (PID: {pid})")
        console.print(f"  URL: http://{host}:{port}")
        console.print(f"  Use [command]paracord console restart[/command] to restart")
        return

    if start_console_daemon(host=host, port=port):
        import time
        time.sleep(0.5)  # Give server time to start

        pid = get_console_pid()
        print_success(f"Console started in background (PID: {pid})")
        console.print(f"  URL: http://{host}:{port}")
        console.print(f"  Logs: {CONSOLE_LOG_FILE}")

        if open_browser:
            import webbrowser
            time.sleep(0.5)
            webbrowser.open(f"http://{host}:{port}")
    else:
        print_error("Failed to start console")
        raise typer.Exit(1)


@console_app.command(name="stop")
def console_stop() -> None:
    """Stop the background console server."""
    if not is_console_running():
        print_info("Console is not running")
        return

    pid = get_console_pid()
    if stop_console_daemon():
        print_success(f"Console stopped (was PID: {pid})")
    else:
        print_error("Failed to stop console")
        raise typer.Exit(1)


@console_app.command(name="restart")
def console_restart(
    host: Annotated[
        str,
        typer.Option("--host", "-h", help="Host to bind to"),
    ] = "127.0.0.1",
    port: Annotated[
        int,
        typer.Option("--port", "-p", help="Port to listen on"),
    ] = CONSOLE_PORT,
) -> None:
    """Restart the background console server."""
    # Check if console is installed
    if not is_console_installed():
        if not prompt_install_console():
            raise typer.Exit(1)

    was_running = is_console_running()

    if was_running:
        print_info("Stopping console...")
        stop_console_daemon()
        import time
        time.sleep(0.5)

    print_info("Starting console...")
    if start_console_daemon(host=host, port=port):
        import time
        time.sleep(0.5)

        pid = get_console_pid()
        print_success(f"Console restarted (PID: {pid})")
        console.print(f"  URL: http://{host}:{port}")
    else:
        print_error("Failed to start console")
        raise typer.Exit(1)


@console_app.command(name="remove")
def console_remove() -> None:
    """Remove the current project from the registry."""
    cwd = Path.cwd()

    project = get_project(cwd)
    if not project:
        print_warning("This directory is not registered as a project.")
        return

    project_name = project.get("name", cwd.name)

    confirm = typer.confirm(f"Remove project '{project_name}' from registry?", default=False)
    if not confirm:
        print_info("Cancelled.")
        return

    if remove_project(cwd):
        print_success(f"Removed project: {project_name}")
    else:
        print_error("Failed to remove project.")


@console_app.command(name="uninstall")
def console_uninstall() -> None:
    """Remove the console environment."""
    if not CONSOLE_DIR.exists():
        print_info("Console environment not installed.")
        return

    confirm = typer.confirm(
        f"Remove console environment at {CONSOLE_DIR}?",
        default=False,
    )
    if not confirm:
        print_info("Cancelled.")
        return

    import shutil

    shutil.rmtree(CONSOLE_DIR)
    print_success("Console environment removed.")


# --- Tunnel subcommands ---


@tunnel_app.command(name="config")
def tunnel_config(
    domain: Annotated[
        str,
        typer.Option("--domain", "-d", help="Base domain for subdomains"),
    ] = "example.com",
    tunnel_name: Annotated[
        str,
        typer.Option("--tunnel", "-t", help="Tunnel name"),
    ] = "paracord",
) -> None:
    """Generate cloudflared tunnel configuration."""
    projects = list_projects()

    # Filter projects that have cloudflare subdomains
    projects_with_tunnel = {
        path: project
        for path, project in projects.items()
        if project.get("cloudflare", {}).get("subdomain")
    }

    if not projects_with_tunnel:
        print_warning("No projects have Cloudflare subdomains configured.")
        console.print("  Use [command]paracord console add --subdomain <name>[/command] to add one.")
        return

    config = generate_tunnel_config(projects_with_tunnel, tunnel_name=tunnel_name, domain=domain)

    print_header("Cloudflared Configuration")
    console.print()
    console.print(f"[dim]# Save to ~/.cloudflared/config.yml[/dim]")
    console.print()
    console.print(config)


@tunnel_app.command(name="instructions")
def tunnel_instructions(
    tunnel_name: Annotated[
        str,
        typer.Option("--tunnel", "-t", help="Tunnel name"),
    ] = "paracord",
) -> None:
    """Show Cloudflare tunnel setup instructions."""
    instructions = get_tunnel_instructions(tunnel_name)
    console.print(instructions)
