"""
Module: sandbox_command
Description: Sandbox lifecycle management commands (start, stop, status).

Implements:
    - docs/cli/reference.md#huitzo-sandbox
    - docs/cli/agent-integration.md#sandbox-lifecycle

See Also:
    - docs/guides/developer-environment.md#development-session-flow
"""

from __future__ import annotations

import json
import logging
import os
import signal
import subprocess
import sys
import time
from pathlib import Path
from typing import TYPE_CHECKING

import httpx
import typer

from huitzo_cli.output import get_output

if TYPE_CHECKING:
    from huitzo_cli.output import OutputContext
from huitzo_cli.sandbox.connection import (
    DEFAULT_PIDFILE,
    cleanup_sidecar,
    read_sidecar,
)

logger = logging.getLogger(__name__)

sandbox_app = typer.Typer(help="Manage local sandbox")


@sandbox_app.command()
def start(
    ctx: typer.Context,
    port: int = typer.Option(8080, "--port", help="Sandbox port"),
    no_tls: bool = typer.Option(False, "--no-tls", help="Run without TLS (disables auth)"),
    background: bool = typer.Option(False, "--background", "-b", help="Run in background"),
    pidfile: str = typer.Option(str(DEFAULT_PIDFILE), "--pidfile", help="PID file path"),
    use_platform: bool = typer.Option(
        False,
        "--use-platform",
        help="Enable real platform services (LLM, HTTP, files)",
    ),
    token: str | None = typer.Option(
        None, "--token", help="Auth token (auto-generated if omitted)"
    ),
) -> None:
    """Start the local sandbox server.

    In foreground mode (default), blocks until Ctrl+C.
    In background mode (--background), spawns a child process and exits.
    """
    output = get_output(ctx)
    pf = Path(pidfile)

    # Check for existing sandbox
    if pf.exists():
        info = read_sidecar(pf)
        if info and _is_process_alive(info.get("pid", 0)):
            output.error(
                "SandboxError",
                f"Sandbox already running (PID {info['pid']}). Stop it first or use a different --pidfile.",
                1,
            )
            raise typer.Exit(1)
        # Stale pidfile — clean up
        _cleanup_pidfile(pf)

    if background:
        _start_background(output, port, no_tls, use_platform, pf, token)
    else:
        _start_foreground(output, port, no_tls, use_platform, pf, token)


@sandbox_app.command()
def stop(
    ctx: typer.Context,
    pidfile: str = typer.Option(str(DEFAULT_PIDFILE), "--pidfile", help="PID file path"),
) -> None:
    """Stop a running background sandbox."""
    output = get_output(ctx)
    pf = Path(pidfile)

    if not pf.exists():
        output.error("SandboxError", "No sandbox pidfile found. Is a sandbox running?", 1)
        raise typer.Exit(1)

    try:
        pid = int(pf.read_text().strip())
    except (ValueError, OSError):
        output.error("SandboxError", f"Invalid pidfile: {pf}", 1)
        _cleanup_pidfile(pf)
        raise typer.Exit(1)

    if not _is_process_alive(pid):
        output.print(f"[yellow]Process {pid} is not running (stale pidfile)[/yellow]")
        _cleanup_pidfile(pf)
        output.success({"stopped": True, "pid": pid, "stale": True})
        return

    # Send SIGTERM
    output.print(f"[bold]Stopping sandbox (PID {pid})...[/bold]")
    try:
        os.kill(pid, signal.SIGTERM)
    except OSError as e:
        output.error("SandboxError", f"Failed to send SIGTERM: {e}", 1)
        raise typer.Exit(1)

    # Wait up to 5 seconds
    for _ in range(50):
        if not _is_process_alive(pid):
            break
        time.sleep(0.1)
    else:
        # Force kill
        output.print("[yellow]Sandbox did not stop gracefully, sending SIGKILL...[/yellow]")
        try:
            os.kill(pid, signal.SIGKILL)
        except OSError:
            pass

    _cleanup_pidfile(pf)
    output.print(f"[green]Sandbox stopped (PID {pid})[/green]")
    output.success({"stopped": True, "pid": pid})


@sandbox_app.command()
def status(
    ctx: typer.Context,
    pidfile: str = typer.Option(str(DEFAULT_PIDFILE), "--pidfile", help="PID file path"),
) -> None:
    """Show status of the local sandbox."""
    output = get_output(ctx)
    pf = Path(pidfile)

    if not pf.exists():
        output.print("[dim]No sandbox running[/dim]")
        output.success({"running": False})
        return

    info = read_sidecar(pf)
    if not info:
        output.print("[yellow]Pidfile exists but no sidecar info[/yellow]")
        output.success({"running": False, "stale_pidfile": True})
        return

    pid = info.get("pid", 0)
    url = info.get("url", "")
    alive = _is_process_alive(pid)

    if not alive:
        output.print(f"[yellow]Sandbox (PID {pid}) is not running (stale)[/yellow]")
        _cleanup_pidfile(pf)
        output.success({"running": False, "pid": pid, "stale": True})
        return

    # Check health endpoint
    commands_count = info.get("commands", 0)
    healthy = False
    try:
        # verify=False only for localhost self-signed certs
        is_localhost = any(h in url for h in ("localhost", "127.0.0.1", "[::1]"))
        resp = httpx.get(f"{url}/health", timeout=3, verify=not is_localhost)
        healthy = resp.status_code == 200
    except httpx.HTTPError:
        pass

    output.print(f"[green]Sandbox running[/green] (PID {pid})")
    output.print(f"  URL:      {url}")
    output.print(f"  Commands: {commands_count}")
    output.print(f"  Health:   {'[green]ok[/green]' if healthy else '[red]unhealthy[/red]'}")
    output.success(
        {
            "running": True,
            "pid": pid,
            "url": url,
            "commands": commands_count,
            "healthy": healthy,
        }
    )


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _start_foreground(
    output: OutputContext,
    port: int,
    no_tls: bool,
    use_platform: bool,
    pidfile: Path,
    token: str | None,
) -> None:
    """Start sandbox in the foreground, blocking until Ctrl+C."""
    from huitzo_cli.sandbox.connection import write_sidecar
    from huitzo_cli.sandbox.lifecycle import (
        create_and_start_sandbox,
        install_editable,
        validate_pack,
    )

    validate_pack(output)
    install_editable(output)

    auth_token = token or ""
    sandbox, base_url, commands = create_and_start_sandbox(
        output, auth_token, port=port, use_tls=not no_tls, use_platform=use_platform
    )

    # Write PID + sidecar (restricted permissions)
    pid = os.getpid()
    pidfile.parent.mkdir(parents=True, exist_ok=True)
    if os.name != "nt":
        fd = os.open(str(pidfile), os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
        with os.fdopen(fd, "w") as f:
            f.write(str(pid))
    else:
        pidfile.write_text(str(pid))
    active_token = sandbox.auth_token or token
    write_sidecar(pidfile, base_url, active_token, len(commands))

    output.print(f"\n[green]Sandbox ready at {base_url}[/green]")
    output.success(
        {
            "url": base_url,
            "pid": pid,
            "commands": len(commands),
            "token_source": str(pidfile.with_suffix(".json")),
        }
    )

    output.print("Press Ctrl+C to stop...")
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        pass
    finally:
        sandbox.stop()
        _cleanup_pidfile(pidfile)
        output.print("\n[yellow]Sandbox stopped[/yellow]")


def _start_background(
    output: OutputContext,
    port: int,
    no_tls: bool,
    use_platform: bool,
    pidfile: Path,
    token: str | None,
) -> None:
    """Spawn sandbox as a background child process."""
    cmd = [
        sys.executable,
        "-m",
        "huitzo_cli.commands._sandbox_serve",
        "_sandbox-serve",
        "--port",
        str(port),
        "--pidfile",
        str(pidfile),
    ]
    if no_tls:
        cmd.append("--no-tls")
    if use_platform:
        cmd.append("--use-platform")

    # Pass token via env var (not CLI arg) to avoid /proc/pid/cmdline exposure
    env = os.environ.copy()
    if token:
        env["_HUITZO_SANDBOX_TOKEN"] = token

    output.print("[bold]Starting sandbox in background...[/bold]")

    proc = subprocess.Popen(
        cmd,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        start_new_session=True,
        env=env,
    )

    # Wait for startup info on stdout (up to 30 seconds)
    startup_info = None
    try:
        if proc.stdout:
            # Read one line of JSON from child
            line = b""
            deadline = time.time() + 30
            while time.time() < deadline:
                if proc.poll() is not None:
                    # Child exited prematurely
                    break
                import select

                ready, _, _ = select.select([proc.stdout], [], [], 1.0)
                if ready:
                    line = proc.stdout.readline()
                    break

            if line:
                startup_info = json.loads(line.decode().strip())
    except (json.JSONDecodeError, OSError):
        pass

    # Close pipes to avoid FD leak (child runs independently)
    if proc.stdout:
        proc.stdout.close()
    if proc.stderr and not (proc.poll() is not None and startup_info is None):
        proc.stderr.close()

    if startup_info and "url" in startup_info:
        output.print(
            f"[green]Sandbox started at {startup_info['url']} (PID {startup_info['pid']})[/green]"
        )
        output.success(startup_info)
    elif proc.poll() is not None:
        stderr_text = proc.stderr.read().decode() if proc.stderr else ""
        if proc.stderr:
            proc.stderr.close()
        output.error("SandboxError", f"Sandbox failed to start: {stderr_text[:500]}", 1)
        _cleanup_pidfile(pidfile)
        raise typer.Exit(1)
    else:
        # Process is running but we didn't get startup info — check pidfile
        time.sleep(2)
        info = read_sidecar(pidfile)
        if info:
            output.print(f"[green]Sandbox started at {info['url']} (PID {info['pid']})[/green]")
            output.success(info)
        else:
            output.print(
                "[yellow]Sandbox may be starting... check 'huitzo sandbox status'[/yellow]"
            )
            output.success({"pid": proc.pid, "starting": True})


def _is_process_alive(pid: int) -> bool:
    """Check if a process with the given PID is running."""
    if pid <= 0:
        return False
    try:
        os.kill(pid, 0)
        return True
    except OSError:
        return False


def _cleanup_pidfile(pidfile: Path) -> None:
    """Remove pidfile and its sidecar."""
    if pidfile.exists():
        try:
            pidfile.unlink()
        except OSError:
            pass
    cleanup_sidecar(pidfile)
