"""Server CLI commands — connection management and ASGI server start.

Provides the ``agents server connect`` stub command, the
``agents server start`` command that launches the uvicorn-backed
ASGI server, and server-mode status helpers.
"""

from __future__ import annotations

from collections.abc import Callable
from pathlib import Path
from typing import Annotated, Any

import typer
from pydantic import ValidationError
from rich.console import Console
from rich.panel import Panel

from cleveragents.a2a.server_config import ServerConnectionConfig
from cleveragents.application.container import get_container
from cleveragents.application.services.config_service import ConfigService
from cleveragents.cli.formatting import OutputFormat, format_output

uvicorn_run: Callable[..., None] | None
try:
    from uvicorn import run as uvicorn_run
except ModuleNotFoundError:
    uvicorn_run = None

app: Any = typer.Typer(help="Server management — start, connect, and status.")
console: Console = Console()

# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------

_STUB_WARNING: str = (
    "Server connection is not yet implemented. "
    "The URL has been saved to configuration but no connection will be made."
)

_LOG_LEVELS: tuple[str, ...] = (
    "critical",
    "error",
    "warning",
    "info",
    "debug",
    "trace",
)


def _normalize_log_level(value: str) -> str:
    """Normalize and validate ``--log-level`` values."""
    normalized = value.lower()
    if normalized not in _LOG_LEVELS:
        options = ", ".join(_LOG_LEVELS)
        raise typer.BadParameter(f"must be one of: {options}")
    return normalized


def _get_config_service() -> ConfigService:
    """Return a ``ConfigService`` wired to the standard config paths.

    Paths are computed lazily from ``Path.home()`` so that changes to
    ``HOME`` (e.g. in tests) are picked up correctly.
    """
    config_dir = Path.home() / ".cleveragents"
    config_path = config_dir / "config.toml"
    container = get_container()
    return ConfigService(
        config_dir=config_dir,
        config_path=config_path,
        event_bus=container.event_bus(),
    )


def _snapshot_config_file(config_path: Path) -> bytes | None:
    """Capture the current config file bytes, if present."""
    if not config_path.exists():
        return None
    return config_path.read_bytes()


def _restore_config_file(config_path: Path, snapshot: bytes | None) -> None:
    """Restore config.toml to a previously captured snapshot."""
    if snapshot is None:
        if config_path.exists():
            config_path.unlink()
        return

    config_path.parent.mkdir(parents=True, exist_ok=True)
    config_path.write_bytes(snapshot)


def _server_connect_updates(
    config: ServerConnectionConfig,
) -> tuple[tuple[str, Any], ...]:
    """Return ordered key updates performed by ``server_connect``."""
    return (
        ("server.url", config.server_url),
        ("server.namespace", config.namespace),
        ("server.tls-verify", config.tls_verify),
    )


def resolve_server_mode() -> str:
    """Determine the current server mode.

    Returns:
        ``"disabled"`` when no server URL is configured, ``"stubbed"``
        when a URL is present but the client is not yet implemented.
    """
    svc = _get_config_service()
    try:
        resolved = svc.resolve("server.url")
        if resolved.value is not None and str(resolved.value).strip():
            return "stubbed"
    except (ValueError, KeyError):
        pass
    return "disabled"


# ---------------------------------------------------------------------------
# Commands
# ---------------------------------------------------------------------------


@app.command("connect")
def server_connect(
    server_url: Annotated[
        str,
        typer.Argument(
            help="Server URL to connect to (e.g. https://agents.example.com)"
        ),
    ],
    namespace: Annotated[
        str,
        typer.Option("--namespace", "-n", help="Namespace on the server"),
    ] = "default",
    tls_verify: Annotated[
        bool,
        typer.Option("--tls-verify/--no-tls-verify", help="Verify TLS certificates"),
    ] = True,
    fmt: Annotated[
        str,
        typer.Option("--format", "-f", help="Output format: rich, plain, json, yaml"),
    ] = "rich",
) -> None:
    """Connect to a CleverAgents server (stub).

    Validates and persists the server URL, namespace, and TLS settings
    to the global configuration file.  No actual connection is made —
    server mode is not yet implemented.

    Examples::

        agents server connect https://agents.example.com
        agents server connect https://agents.example.com --namespace my-team
        agents server connect http://localhost:8080 --no-tls-verify
    """
    # Validate the connection config
    try:
        config = ServerConnectionConfig(
            server_url=server_url,
            namespace=namespace,
            tls_verify=tls_verify,
        )
    except ValidationError as exc:
        console.print(f"[red]Invalid server configuration:[/red] {exc}")
        raise typer.Exit(code=1) from exc

    # Persist via set_value() so each key produces a CONFIG_CHANGED audit
    # event. If any write fails, restore the original snapshot and emit
    # compensating CONFIG_CHANGED events so the audit trail reflects the
    # rollback (no stale partial-change trail).
    svc = _get_config_service()
    config_path = svc._config_path
    config_snapshot = _snapshot_config_file(config_path)
    update_items = _server_connect_updates(config)
    existing_config = svc.read_config()
    original_values = {key: existing_config.get(key) for key, _ in update_items}
    applied_updates: list[tuple[str, Any]] = []
    try:
        for key, new_value in update_items:
            svc.set_value(key, new_value)
            applied_updates.append((key, new_value))
    except Exception:
        _restore_config_file(config_path, config_snapshot)
        for key, attempted_value in reversed(applied_updates):
            svc.emit_config_changed(
                key=key,
                old_value=attempted_value,
                new_value=original_values.get(key),
                compensating=True,
                reason="rollback_after_partial_server_connect_failure",
            )
        raise

    result: dict[str, Any] = {
        "server_url": config.server_url,
        "namespace": config.namespace,
        "tls_verify": config.tls_verify,
        "status": "stubbed",
        "warning": _STUB_WARNING,
    }

    if fmt != OutputFormat.RICH.value:
        typer.echo(format_output(result, fmt))
        return

    console.print(
        Panel(
            f"[bold]Server URL:[/bold] {config.server_url}\n"
            f"[bold]Namespace:[/bold] {config.namespace}\n"
            f"[bold]TLS Verify:[/bold] {config.tls_verify}\n"
            f"[bold]Status:[/bold] stubbed\n"
            f"\n[yellow]{_STUB_WARNING}[/yellow]",
            title="Server Connection (Stub)",
            expand=False,
        )
    )


@app.command("status")
def server_status(
    fmt: Annotated[
        str,
        typer.Option("--format", "-f", help="Output format: rich, plain, json, yaml"),
    ] = "rich",
) -> None:
    """Show current server connection status.

    Displays the configured server URL (if any) and whether server mode
    is disabled or stubbed.
    """
    mode = resolve_server_mode()
    svc = _get_config_service()

    server_url: str | None = None
    namespace: str = "default"
    tls_verify: bool = True

    try:
        resolved_url = svc.resolve("server.url")
        if resolved_url.value is not None:
            server_url = str(resolved_url.value)
    except (ValueError, KeyError):
        pass

    try:
        resolved_ns = svc.resolve("server.namespace")
        if resolved_ns.value is not None:
            namespace = str(resolved_ns.value)
    except (ValueError, KeyError):
        pass

    try:
        resolved_tls = svc.resolve("server.tls-verify")
        if resolved_tls.value is not None:
            tls_verify = bool(resolved_tls.value)
    except (ValueError, KeyError):
        pass

    result: dict[str, Any] = {
        "server_mode": mode,
        "server_url": server_url,
        "namespace": namespace,
        "tls_verify": tls_verify,
    }

    if fmt != OutputFormat.RICH.value:
        typer.echo(format_output(result, fmt))
        return

    url_display = server_url if server_url else "(not configured)"
    console.print(
        Panel(
            f"[bold]Server Mode:[/bold] {mode}\n"
            f"[bold]Server URL:[/bold] {url_display}\n"
            f"[bold]Namespace:[/bold] {namespace}\n"
            f"[bold]TLS Verify:[/bold] {tls_verify}",
            title="Server Status",
            expand=False,
        )
    )


@app.command("serve")
def server_serve(
    host: Annotated[
        str,
        typer.Option("--host", help="Bind address for the ASGI server"),
    ] = "0.0.0.0",  # nosec B104
    port: Annotated[
        int,
        typer.Option("--port", min=1, max=65535, help="Bind port for the ASGI server"),
    ] = 8000,
    workers: Annotated[
        int,
        typer.Option("--workers", min=1, help="Number of uvicorn worker processes"),
    ] = 1,
    log_level: Annotated[
        str,
        typer.Option(
            "--log-level",
            help="Uvicorn log level",
            callback=_normalize_log_level,
        ),
    ] = "info",
    app_target: Annotated[
        str,
        typer.Option("--app", help="ASGI app import path (module:attribute)"),
    ] = "cleveragents.a2a.asgi:app",
) -> None:
    """Run the CleverAgents ASGI server process.

    This command is used by container runtimes (Docker/Kubernetes) to launch
    the server endpoint using the project CLI entrypoint (`python -m cleveragents`).
    """
    runner = uvicorn_run
    if runner is None:
        console.print(
            "[red]Unable to start server:[/red] optional dependency "
            "'uvicorn' is not installed."
        )
        raise typer.Exit(code=1)

    runner(
        app_target,
        host=host,
        port=port,
        workers=workers,
        log_level=log_level,
    )


@app.command("start")
def server_start(
    host: Annotated[
        str | None,
        typer.Option("--host", "-H", help="Bind address (default: from settings)"),
    ] = None,
    port: Annotated[
        int | None,
        typer.Option("--port", "-p", help="Bind port (default: from settings)"),
    ] = None,
    log_level: Annotated[
        str | None,
        typer.Option("--log-level", help="Uvicorn log level"),
    ] = None,
) -> None:
    """Start the CleverAgents ASGI server.

    Launches a uvicorn server hosting the A2A JSON-RPC 2.0 endpoint.
    The server binds to host:port from Settings or CLI overrides and
    handles graceful shutdown on SIGTERM/SIGINT.

    Examples::

        agents server start
        agents server start --host 127.0.0.1 --port 9000
        agents server start --log-level debug
    """
    from cleveragents.infrastructure.server import run_server

    console.print("[bold]Starting CleverAgents ASGI server...[/bold]")
    run_server(
        host=host,
        port=port,
        log_level=log_level,
    )


__all__ = [
    "app",
    "resolve_server_mode",
    "server_connect",
    "server_serve",
    "server_start",
    "server_status",
]
