"""
Module: exec_cmd
Description: Direct command execution against a sandbox without the interactive REPL.

Implements:
    - docs/cli/reference.md#huitzo-pack-exec
    - docs/cli/agent-integration.md#direct-command-execution

See Also:
    - docs/cli/agent-integration.md
"""

from __future__ import annotations

import asyncio
import json
import logging
from pathlib import Path
from typing import TYPE_CHECKING, Optional

import typer

from huitzo_cli.errors import (
    AuthenticationError,
    CommandNotFoundError,
    CommandValidationError,
    NetworkError,
    SandboxCommandError,
    ServerError,
)
from huitzo_cli.output import get_output
from huitzo_cli.sandbox.connection import DEFAULT_PIDFILE, resolve_sandbox

if TYPE_CHECKING:
    from typing import Any  # noqa: F401  # used in string annotations

    from huitzo_cli.output import OutputContext

logger = logging.getLogger(__name__)


def exec_command(
    ctx: typer.Context,
    command_name: str = typer.Argument(..., help="Command name to execute"),
    args_json: str = typer.Option("{}", "--args", help="JSON arguments"),
    sandbox_url: Optional[str] = typer.Option(
        None, "--sandbox-url", help="Sandbox URL (e.g., https://localhost:8080)"
    ),
    sandbox_token: Optional[str] = typer.Option(None, "--sandbox-token", help="Sandbox auth token"),
    local: bool = typer.Option(
        False, "--local", help="Auto-start ephemeral sandbox, execute, then stop"
    ),
    port: int = typer.Option(0, "--port", help="Port for ephemeral sandbox (0=random)"),
    no_tls: bool = typer.Option(False, "--no-tls", help="Disable TLS for ephemeral sandbox"),
    use_platform: bool = typer.Option(False, "--use-platform", help="Enable platform services"),
    pidfile: str = typer.Option(str(DEFAULT_PIDFILE), "--pidfile", help="PID file path"),
    file: Optional[list[str]] = typer.Option(
        None, "--file", help="Upload file(s) before execution"
    ),
) -> None:
    """Execute a pack command against a sandbox.

    Connects to an existing sandbox, or starts an ephemeral one with --local.

    Examples:
        huitzo pack exec hello --args '{"name": "World"}'
        huitzo pack exec analyze --local --args '{"text": "..."}'
        huitzo pack exec process --sandbox-url https://localhost:8080
    """
    output = get_output(ctx)

    # Parse args
    try:
        args = json.loads(args_json)
    except json.JSONDecodeError as e:
        output.error("ValidationError", f"Invalid JSON args: {e}", 3)
        raise typer.Exit(1)

    if local:
        _exec_with_ephemeral_sandbox(output, command_name, args, port, no_tls, use_platform, file)
    else:
        _exec_against_sandbox(
            output, command_name, args, sandbox_url, sandbox_token, Path(pidfile), file
        )


def list_commands(
    ctx: typer.Context,
    sandbox_url: Optional[str] = typer.Option(None, "--sandbox-url", help="Sandbox URL"),
    sandbox_token: Optional[str] = typer.Option(None, "--sandbox-token", help="Sandbox auth token"),
    pidfile: str = typer.Option(str(DEFAULT_PIDFILE), "--pidfile", help="PID file path"),
) -> None:
    """List commands available in the sandbox."""
    output = get_output(ctx)

    try:
        url, token = resolve_sandbox(sandbox_url, sandbox_token, Path(pidfile))
    except RuntimeError as e:
        output.error("SandboxError", str(e), 1)
        raise typer.Exit(1)

    result = asyncio.run(_async_list_commands(url, token))
    commands = result.get("commands", [])

    for cmd in commands:
        name = cmd.get("name", "")
        desc = cmd.get("description", "")[:60]
        output.print(f"  {name} — {desc}")

    output.success(result)


def describe_command(
    ctx: typer.Context,
    command_name: str = typer.Argument(..., help="Command name"),
    sandbox_url: Optional[str] = typer.Option(None, "--sandbox-url", help="Sandbox URL"),
    sandbox_token: Optional[str] = typer.Option(None, "--sandbox-token", help="Sandbox auth token"),
    pidfile: str = typer.Option(str(DEFAULT_PIDFILE), "--pidfile", help="PID file path"),
) -> None:
    """Describe a command's metadata and args schema."""
    output = get_output(ctx)

    try:
        url, token = resolve_sandbox(sandbox_url, sandbox_token, Path(pidfile))
    except RuntimeError as e:
        output.error("SandboxError", str(e), 1)
        raise typer.Exit(1)

    result = asyncio.run(_async_describe_command(url, token, command_name))
    if result is None:
        output.error("CommandNotFoundError", f"Command '{command_name}' not found", 3)
        raise typer.Exit(1)

    output.print(f"[bold]{result.get('name', command_name)}[/bold]")
    output.print(f"  Description: {result.get('description', '')}")
    output.print(f"  Namespace:   {result.get('namespace', '')}")
    output.print(f"  Timeout:     {result.get('timeout', 60)}s")
    if result.get("args_schema"):
        output.print_json(result["args_schema"])

    output.success(result)


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


def _exec_against_sandbox(
    output: OutputContext,
    command_name: str,
    args: dict[str, Any],
    sandbox_url: str | None,
    sandbox_token: str | None,
    pidfile: Path,
    files: list[str] | None,
) -> None:
    """Execute against an existing sandbox."""
    try:
        url, token = resolve_sandbox(sandbox_url, sandbox_token, pidfile)
    except RuntimeError as e:
        output.error("SandboxError", str(e), 1)
        raise typer.Exit(1)

    _run_and_output(output, url, token, command_name, args, files)


def _exec_with_ephemeral_sandbox(
    output: OutputContext,
    command_name: str,
    args: dict[str, Any],
    port: int,
    no_tls: bool,
    use_platform: bool,
    files: list[str] | None,
) -> None:
    """Start ephemeral sandbox, execute, then stop."""
    from huitzo_cli.sandbox.lifecycle import (
        create_and_start_sandbox,
        install_editable,
        validate_pack,
    )

    validate_pack(output)
    install_editable(output)

    sandbox, base_url, _commands = create_and_start_sandbox(
        output, token="", port=port, use_tls=not no_tls, use_platform=use_platform
    )

    token = sandbox.auth_token
    try:
        _run_and_output(output, base_url, token, command_name, args, files)
    finally:
        sandbox.stop()


def _run_and_output(
    output: OutputContext,
    url: str,
    token: str | None,
    command_name: str,
    args: dict[str, Any],
    files: list[str] | None,
) -> None:
    """Run async execution and handle errors with structured output."""
    try:
        result = asyncio.run(_async_execute(url, token, command_name, args, files))
    except SandboxCommandError as e:
        output.error("SandboxCommandError", e.user_message, 10)
        raise typer.Exit(10) from e
    except CommandValidationError as e:
        output.error("CommandValidationError", e.user_message, 3)
        raise typer.Exit(3) from e
    except CommandNotFoundError as e:
        output.error("CommandNotFoundError", e.user_message, 3)
        raise typer.Exit(3) from e
    except AuthenticationError as e:
        output.error("AuthenticationError", e.user_message, 2)
        raise typer.Exit(2) from e
    except NetworkError as e:
        output.error("NetworkError", e.user_message, 4)
        raise typer.Exit(4) from e
    except ServerError as e:
        output.error("ServerError", e.user_message, 5)
        raise typer.Exit(5) from e

    output.print_json(result)
    output.success(result)


def _verify_for_url(url: str) -> bool:
    """Return False (skip TLS verify) for localhost, True otherwise."""
    return not any(h in url for h in ("localhost", "127.0.0.1", "[::1]"))


async def _async_execute(
    url: str, token: str | None, command_name: str, args: dict[str, Any], files: list[str] | None
) -> dict[str, Any]:
    """Execute a command via REPLClient."""
    from huitzo_cli.repl.client import REPLClient

    async with REPLClient(url, auth_token=token, verify=_verify_for_url(url)) as client:
        if files:
            for fpath in files:
                await client.upload_file(Path(fpath))

        result: dict[str, Any] = await client.execute_command(command_name, args)
        return result


async def _async_list_commands(url: str, token: str | None) -> dict[str, Any]:
    """List commands via REPLClient."""
    from huitzo_cli.repl.client import REPLClient

    async with REPLClient(url, auth_token=token, verify=_verify_for_url(url)) as client:
        return await client.list_commands()


async def _async_describe_command(url: str, token: str | None, name: str) -> dict[str, Any] | None:
    """Describe a command via REPLClient."""
    from huitzo_cli.repl.client import REPLClient

    async with REPLClient(url, auth_token=token, verify=_verify_for_url(url)) as client:
        return await client.describe_command(name)
