"""
Delegate all arguments to the remotive-topology CLI inside Docker.
Example:
  remotive topology status --help
"""

import os
import shlex
import shutil
import subprocess
import sys
import threading
from datetime import datetime

import typer
from rich.progress import Progress, SpinnerColumn, TextColumn

from remotivelabs.cli.settings import settings
from remotivelabs.cli.topology.context import TopologyContext


def check_container_engine_installed(engine: str) -> None:
    """Check if Docker is installed and accessible."""

    if shutil.which(engine) is None:
        if engine == "docker":
            typer.echo("❌ 'docker' is not installed or not in PATH.", err=True)
            typer.echo("👉 Please install Docker: https://docs.docker.com/get-docker/", err=True)
        elif engine == "podman":
            typer.echo("❌ 'podman' is not installed or not in PATH.", err=True)
            typer.echo("👉 Please install Podman: https://podman.io/getting-started/installation", err=True)
        raise typer.Exit(code=1)


def _run_with_delayed_spinner(cmd: list[str], message: str, delay_seconds: float = 2.0) -> None:
    """
    Run a command and show a spinner only if it takes longer than `delay_seconds`.

    Stdout is suppressed; stderr is forwarded after the process completes.
    Raises subprocess.CalledProcessError if the command exits with a non-zero code.
    """
    process = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=False)

    spinner = Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), transient=True)
    spinner_shown = False

    def show_spinner_after_delay() -> None:
        nonlocal spinner_shown
        if process.poll() is None:  # process is still running
            try:
                spinner.start()
                spinner_shown = True
                spinner.add_task(message, total=None)
            except Exception:
                pass  # Another live display is already active; skip spinner

    timer = threading.Timer(delay_seconds, show_spinner_after_delay)
    timer.start()
    _, stderr = process.communicate()
    timer.cancel()

    if spinner_shown:
        spinner.stop()

    if stderr:
        sys.stderr.buffer.write(stderr)

    if process.returncode != 0:
        raise subprocess.CalledProcessError(process.returncode, cmd)


def _build_pull_command(engine: str, image: str) -> list[str]:
    return [engine, "pull", "-q", image]


def _build_run_command(ctx: TopologyContext, image: str, args: list[str]) -> list[str]:
    container_engine = ctx.container_engine.value
    check_container_engine_installed(container_engine)

    workspace = str(ctx.workspace or ctx.working_directory)
    container_workspace = "/data" if sys.platform == "win32" else workspace

    # Pass the workspace path into the container via env var when explicitly set
    workspace_env = ["-e", f"REMOTIVE_TOPOLOGY_WORKSPACE={ctx.workspace}"] if ctx.workspace is not None else []
    workspace_dir_env = ["-e", f"REMOTIVE_TOPOLOGY_WORKING_DIRECTORY={str(ctx.working_directory)}"]

    cmd = [container_engine, "run", "--rm"]

    # -u flag maps host user into the container; not available on Windows
    if hasattr(os, "getuid") and hasattr(os, "getgid"):
        cmd += ["-u", f"{os.getuid()}:{os.getgid()}"]  # type: ignore[unused-ignore, attr-defined]

    cmd += [
        "-v",
        f"{os.path.expanduser('~')}/.config/remotive/:/.config/remotive",
        "-v",
        f"{workspace}:{container_workspace}",
        "-w",
        container_workspace,
    ]
    cmd += workspace_env
    cmd += workspace_dir_env
    cmd += [
        "-e",
        f"REMOTIVE_CLOUD_ORGANIZATION={os.environ.get('REMOTIVE_CLOUD_ORGANIZATION', '')}",
        "-e",
        f"REMOTIVE_CLOUD_AUTH_TOKEN={os.environ.get('REMOTIVE_CLOUD_AUTH_TOKEN', '')}",
        "-e",
        "REMOTIVE_CONFIG_DIR=/.config/remotive",
    ]
    if ctx.is_studio:
        cmd += ["-e", "REMOTIVE_STUDIO=true"]
    cmd += [image] + args
    return cmd


def run_topology_cli_in_container(ctx: TopologyContext, args: list[str], capture_output: bool) -> subprocess.CompletedProcess[str]:
    """
    Run the topology CLI in a container.


    The spinner only appears when the pull actually takes time (i.e. a real download).
    Since the pull check may take a few seconds this can be a bit annoying, only do it once every 30 minutes

    Args:
        ctx: The context of the topology CLI
        args: The arguments to pass to the topology CLI
        capture_output: Whether to capture the output of the topology CLI
    """
    container_engine = ctx.container_engine.value
    check_container_engine_installed(container_engine)

    _default_image = "remotivelabs/remotive-topology:0.21"
    image = ctx.image or _default_image
    if ctx.container_registry:
        image = f"{ctx.container_registry.rstrip('/')}/{image}"

    pull_cmd = _build_pull_command(container_engine, image)
    run_cmd = _build_run_command(ctx, image, args)

    if os.environ.get("DBG") == "true":
        typer.echo(f"[DBG] {shlex.join(run_cmd)}", err=True)

    try:
        if settings.should_perform_topology_pull():
            _run_with_delayed_spinner(pull_cmd, "Checking for updates...")
            settings.set_last_topology_pull_time(datetime.now().isoformat())

        return subprocess.run(run_cmd, check=True, capture_output=capture_output, text=capture_output)
    except FileNotFoundError:
        typer.echo(f"❌ {container_engine} not found. Make sure {container_engine} is installed and in PATH.", err=True)
        raise typer.Exit(code=1)
