from __future__ import annotations

import os
import sys
from pathlib import Path

import httpx
import typer
from rich.console import Console
from rich.panel import Panel

from ..config import AppConfig
from ..server import run_server
from .app import app, console
from .shared import (
    DEFAULT_PID_FILE,
    DEFAULT_SOCKET_PATH,
    format_network_urls,
    get_network_interfaces,
    print_qr_code,
    read_pid_file,
    remove_pid_file,
    write_pid_file,
)


def _print_primary_qr(console: Console, port: int) -> None:
    interfaces = get_network_interfaces()
    for _iface, ip, is_primary in interfaces:
        if is_primary:
            print_qr_code(console, f"http://{ip}:{port}")
            break


def _print_started_panel(
    console: Console,
    *,
    daemon: bool,
    network_urls: str,
    socket_path: Path,
    pid: int | None = None,
) -> None:
    if daemon and pid is not None:
        body = (
            f"[bold green]Claude Board Server (daemon)[/bold green]\n\n"
            f"{network_urls}\n\n"
            f"[dim]RPC Socket:[/dim]  {socket_path}\n"
            f"[dim]PID:[/dim]         {pid}\n\n"
            "[yellow]Open the URL on your phone to approve/deny requests[/yellow]\n\n"
            "Stop with: [bold]claude-board stop[/bold]"
        )
        title = "Server Started"
    else:
        body = (
            f"[bold green]Claude Board Server[/bold green]\n\n"
            f"{network_urls}\n\n"
            f"[dim]RPC Socket:[/dim]  {socket_path}\n\n"
            "[yellow]Open the URL on your phone to approve/deny requests[/yellow]\n\n"
            "Press [bold]Ctrl+C[/bold] to stop"
        )
        title = "Starting Server"

    console.print(Panel.fit(body, title=title, border_style="green"))


def _wait_for_daemon_and_report(
    console: Console,
    *,
    port: int,
    socket_path: Path,
    network_urls: str,
    open_browser: bool,
) -> None:
    import time  # noqa: PLC0415

    for _ in range(20):
        time.sleep(0.1)
        daemon_pid = read_pid_file()
        if daemon_pid:
            try:
                httpx.get(f"http://localhost:{port}/api/health", timeout=1)
                break
            except (httpx.ConnectError, httpx.TimeoutException, OSError):
                continue

    daemon_pid = read_pid_file()
    if not daemon_pid:
        console.print("[red]Failed to start daemon[/red]")
        console.print(f"[dim]Check log: {DEFAULT_PID_FILE.parent / 'server.log'}[/dim]")
        raise typer.Exit(1)

    _print_started_panel(
        console,
        daemon=True,
        network_urls=network_urls,
        socket_path=socket_path,
        pid=daemon_pid,
    )
    _print_primary_qr(console, port)

    if open_browser:
        import webbrowser  # noqa: PLC0415

        webbrowser.open(f"http://localhost:{port}")


def _daemonize_process() -> None:
    os.setsid()
    try:
        pid = os.fork()
        if pid > 0:
            sys.exit(0)
    except OSError:
        sys.exit(1)


def _redirect_stdio_to_log() -> None:
    log_file = DEFAULT_PID_FILE.parent / "server.log"
    log_file.parent.mkdir(parents=True, exist_ok=True)

    sys.stdout.flush()
    sys.stderr.flush()
    with Path("/dev/null").open() as devnull:
        os.dup2(devnull.fileno(), sys.stdin.fileno())
    with log_file.open("a") as log:
        os.dup2(log.fileno(), sys.stdout.fileno())
        os.dup2(log.fileno(), sys.stderr.fileno())


def _run_daemon_server(host: str, port: int, socket_path: Path) -> None:
    write_pid_file(os.getpid())
    _redirect_stdio_to_log()

    try:
        config = AppConfig.load()
        config.server.host = host
        config.server.port = port
        run_server(host=host, port=port, config=config, socket_path=socket_path)
    finally:
        remove_pid_file()
        if socket_path.exists():
            socket_path.unlink()


def _run_foreground_server(
    console: Console,
    *,
    host: str,
    port: int,
    socket_path: Path,
    network_urls: str,
    open_browser: bool,
) -> None:
    _print_started_panel(
        console,
        daemon=False,
        network_urls=network_urls,
        socket_path=socket_path,
    )
    _print_primary_qr(console, port)

    if open_browser:
        import webbrowser  # noqa: PLC0415

        webbrowser.open(f"http://localhost:{port}")

    write_pid_file(os.getpid())
    try:
        config = AppConfig.load()
        config.server.host = host
        config.server.port = port
        run_server(host=host, port=port, config=config, socket_path=socket_path)
    except KeyboardInterrupt:
        console.print("\n[yellow]Server stopped.[/yellow]")
    finally:
        remove_pid_file()
        if socket_path.exists():
            socket_path.unlink()


@app.command(name="serve")
def serve_command(
    host: str = typer.Option(
        "0.0.0.0",
        "--host",
        "-h",
        help="Host to bind to (0.0.0.0 for all interfaces).",
    ),
    port: int = typer.Option(
        8765,
        "--port",
        "-p",
        help="Port to listen on.",
    ),
    rpc_socket: str | None = typer.Option(
        None,
        "--rpc-socket",
        help=f"Unix socket path for RPC API (default: {DEFAULT_SOCKET_PATH}).",
    ),
    *,
    open_browser: bool = typer.Option(
        False, "--open", "-o", help="Open browser after starting server."
    ),
    daemon: bool = typer.Option(
        False, "--daemon", "-d", help="Run server in background (daemon mode)."
    ),
) -> None:
    existing_pid = read_pid_file()
    if existing_pid:
        console.print(f"[yellow]Server already running (PID: {existing_pid})[/yellow]")
        console.print("Use [bold]claude-board stop[/bold] to stop it first.")
        raise typer.Exit(1)

    socket_path = Path(rpc_socket) if rpc_socket else DEFAULT_SOCKET_PATH
    network_urls = format_network_urls(port)

    if daemon:
        try:
            pid = os.fork()
            if pid > 0:
                _wait_for_daemon_and_report(
                    console,
                    port=port,
                    socket_path=socket_path,
                    network_urls=network_urls,
                    open_browser=open_browser,
                )
                raise typer.Exit(0)
        except OSError as e:
            console.print(f"[red]Fork failed: {e}[/red]")
            raise typer.Exit(1) from None

        _daemonize_process()
        _run_daemon_server(host, port, socket_path)
        return

    _run_foreground_server(
        console,
        host=host,
        port=port,
        socket_path=socket_path,
        network_urls=network_urls,
        open_browser=open_browser,
    )


@app.command(name="stop")
def stop_command() -> None:
    pid = read_pid_file()
    if not pid:
        console.print("[yellow]No server is running.[/yellow]")
        raise typer.Exit(0)

    try:
        import signal  # noqa: PLC0415

        os.kill(pid, signal.SIGTERM)
        console.print(f"[green]Server stopped (PID: {pid})[/green]")
        remove_pid_file()
        if DEFAULT_SOCKET_PATH.exists():
            DEFAULT_SOCKET_PATH.unlink()
    except ProcessLookupError:
        console.print("[yellow]Server process not found, cleaning up...[/yellow]")
        remove_pid_file()
    except PermissionError:
        console.print(f"[red]Permission denied to stop server (PID: {pid})[/red]")
        raise typer.Exit(1) from None
