"""CLI command for trajectory web viewer."""

import os
import socket
import subprocess
from pathlib import Path
from typing import Annotated

from rich.console import Console
from typer import Argument, Option

console = Console(stderr=True)

# Path to static viewer files (built in CI)
STATIC_DIR = Path(__file__).parent.parent / "viewer" / "static"

# Path to viewer source (for dev mode)
VIEWER_DIR = Path(__file__).parent.parent.parent.parent / "viewer"


def _parse_port_range(port_str: str) -> tuple[int, int]:
    """Parse a port or port range string into (start, end) tuple."""
    if "-" in port_str:
        parts = port_str.split("-")
        return int(parts[0]), int(parts[1])
    port = int(port_str)
    return port, port


def _has_bun() -> bool:
    """Check if bun is available."""
    import shutil

    return shutil.which("bun") is not None


def _build_viewer() -> bool:
    """Build the viewer frontend and copy to static directory.

    Returns True on success, False on failure.
    """
    if not VIEWER_DIR.exists():
        console.print(f"[red]Error:[/red] Viewer source not found at {VIEWER_DIR}")
        return False

    if not _has_bun():
        console.print(
            "[red]Error:[/red] bun is required to build the viewer. "
            "Install it from https://bun.com"
        )
        return False

    console.print("[blue]Building viewer...[/blue]")

    # Install dependencies
    console.print("  Installing dependencies...")
    result = subprocess.run(
        ["bun", "install"],
        cwd=VIEWER_DIR,
        capture_output=True,
        text=True,
    )
    if result.returncode != 0:
        console.print("[red]Error:[/red] Failed to install dependencies")
        console.print(result.stderr)
        return False

    # Build
    console.print("  Building frontend...")
    result = subprocess.run(
        ["bun", "run", "build"],
        cwd=VIEWER_DIR,
        capture_output=True,
        text=True,
    )
    if result.returncode != 0:
        console.print("[red]Error:[/red] Build failed")
        console.print(result.stderr)
        return False

    # Copy build output to static directory
    build_client_dir = VIEWER_DIR / "build" / "client"
    if not build_client_dir.exists():
        console.print(f"[red]Error:[/red] Build output not found at {build_client_dir}")
        return False

    import shutil

    # Remove existing static dir if present
    if STATIC_DIR.exists():
        shutil.rmtree(STATIC_DIR)

    # Copy build output
    shutil.copytree(build_client_dir, STATIC_DIR)
    console.print("[green]Build complete![/green]")
    return True


def _find_available_port(host: str, start: int, end: int) -> int | None:
    """Find an available port in the given range."""
    for port in range(start, end + 1):
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
                s.bind((host, port))
                return port
        except OSError:
            continue
    return None


def view_command(
    folder: Annotated[
        Path,
        Argument(
            help="Folder containing job/trial directories with trajectories",
        ),
    ],
    port: Annotated[
        str,
        Option(
            "--port",
            "-p",
            help="Port or port range (e.g., 8080 or 8080-8090)",
        ),
    ] = "8080-8089",
    host: Annotated[
        str,
        Option(
            "--host",
            help="Host to bind the server to",
        ),
    ] = "127.0.0.1",
    dev: Annotated[
        bool,
        Option(
            "--dev",
            help="Run frontend in development mode with hot reloading",
        ),
    ] = False,
    no_build: Annotated[
        bool,
        Option(
            "--no-build",
            help="Skip auto-building viewer if static files are missing",
        ),
    ] = False,
    build: Annotated[
        bool,
        Option(
            "--build",
            help="Force rebuild of the viewer even if static files exist",
        ),
    ] = False,
) -> None:
    """Start a web server to browse and view trajectories.

    Scans the specified folder for jobs/trials containing trajectory files
    and serves an interactive web UI for browsing them.

    Example usage:
        harbor view ./jobs
        harbor view ./jobs --port 9000
        harbor view ./jobs --port 8080-8090
        harbor view ./jobs --dev
        harbor view ./jobs --build
    """
    folder = folder.expanduser().resolve()
    if not folder.exists():
        console.print(f"[red]Error:[/red] Folder '{folder}' does not exist")
        raise SystemExit(1)

    start_port, end_port = _parse_port_range(port)
    backend_port = _find_available_port(host, start_port, end_port)

    if backend_port is None:
        console.print(
            f"[red]Error:[/red] No available port found in range {start_port}-{end_port}"
        )
        raise SystemExit(1)

    if dev:
        if build:
            console.print(
                "[yellow]Warning:[/yellow] --build is ignored in dev mode "
                "(dev mode uses hot reloading instead of static files)"
            )
            console.print()
        _run_dev_mode(folder, host, backend_port)
    else:
        _run_production_mode(folder, host, backend_port, no_build=no_build, build=build)


def _run_production_mode(
    folder: Path, host: str, port: int, *, no_build: bool = False, build: bool = False
) -> None:
    """Run in production mode with static files served from the package."""
    import uvicorn

    from harbor.viewer import create_app

    static_dir = STATIC_DIR if STATIC_DIR.exists() else None

    # Force build if requested
    if build:
        if not VIEWER_DIR.exists():
            console.print(
                f"[red]Error:[/red] Cannot build: viewer source not found at {VIEWER_DIR}"
            )
            raise SystemExit(1)
        console.print("[blue]Force rebuilding viewer...[/blue]")
        console.print()
        if _build_viewer():
            static_dir = STATIC_DIR
        else:
            console.print()
            console.print("[red]Build failed.[/red]")
            raise SystemExit(1)
    # Auto-build if static files are missing and we have the source
    elif static_dir is None and not no_build and VIEWER_DIR.exists():
        console.print(
            "[yellow]Viewer not built.[/yellow] Building now (this only happens once)..."
        )
        console.print()
        if _build_viewer():
            static_dir = STATIC_DIR
        else:
            console.print()
            console.print("[yellow]Build failed.[/yellow] Starting in API-only mode.")
            console.print("  You can also try --dev flag for development mode.")
            console.print()

    if static_dir is None:
        console.print(
            "[yellow]Warning:[/yellow] Viewer static files not found. "
            "Only API will be available."
        )
        console.print("  Use --dev flag for development mode with hot reloading.")
        console.print()

    app = create_app(folder, static_dir=static_dir)

    console.print("[green]Starting Harbor Viewer[/green]")
    console.print(f"  Jobs folder: {folder}")
    console.print(f"  Server: http://{host}:{port}")
    if static_dir is None:
        console.print(f"  API docs: http://{host}:{port}/docs")
    console.print()

    config = uvicorn.Config(app, host=host, port=port, log_level="info")
    server = uvicorn.Server(config)
    server.run()


def _run_dev_mode(folder: Path, host: str, backend_port: int) -> None:
    """Run in development mode with separate backend and frontend dev server."""
    import uvicorn

    if not VIEWER_DIR.exists():
        console.print(f"[red]Error:[/red] Viewer directory not found at {VIEWER_DIR}")
        console.print("  Dev mode requires the viewer source code.")
        raise SystemExit(1)

    if not _has_bun():
        console.print(
            "[red]Error:[/red] bun is required for dev mode. "
            "Install it from https://bun.com"
        )
        raise SystemExit(1)

    console.print("[green]Starting Harbor Viewer (dev mode)[/green]")
    console.print(f"  Jobs folder: {folder}")
    console.print(f"  Backend API: http://{host}:{backend_port}")
    console.print("  Frontend: http://localhost:5173")
    console.print()

    # Start frontend dev server in subprocess
    frontend_env = os.environ.copy()
    frontend_env["VITE_API_URL"] = f"http://{host}:{backend_port}"

    frontend_proc = subprocess.Popen(
        ["bun", "dev"],
        cwd=VIEWER_DIR,
        env=frontend_env,
    )

    def cleanup_frontend() -> None:
        frontend_proc.terminate()
        try:
            frontend_proc.wait(timeout=5)
        except subprocess.TimeoutExpired:
            frontend_proc.kill()

    # Set environment variable for the factory function to use
    os.environ["HARBOR_VIEWER_JOBS_DIR"] = str(folder)

    # Run backend - uvicorn handles SIGINT/SIGTERM and shuts down gracefully
    try:
        uvicorn.run(
            "harbor.viewer:create_app_from_env",
            host=host,
            port=backend_port,
            log_level="info",
            reload=True,
            reload_dirs=[str(Path(__file__).parent.parent / "viewer")],
        )
    finally:
        # Always clean up frontend after uvicorn exits
        cleanup_frontend()
