# tools/cli/runner.py - Generic tool for running Mugi-installed CLI programs.
# Resolves binaries from Mugi Homebrew, CLI venvs, or system PATH.
# Also provides cli_rich_path() used by other CLI tools and shell_execute.
from __future__ import annotations

import os
import shutil
import subprocess

from langchain_core.tools import tool

from persistence import MUGI_DIR, CLI_VENVS_DIR
from tools._logging import _debug_log, _emit_input_status
from tools.live_view_visuals import emit_live_view_preview

_MUGI_HOMEBREW_BIN = os.path.join(MUGI_DIR, "homebrew", "bin")
_CLI_RUNNER_TIMEOUT = 120  # 2 minutes default; caller can override via max_seconds


def cli_rich_path() -> str:
    """Build a PATH string that includes Mugi-installed CLI locations.

    Order: ~/.mugi/homebrew/bin → ~/.mugi/cli-venvs/*/bin → /opt/homebrew/bin →
           /usr/local/bin → /usr/bin → /bin → current env PATH.

    This is used by run_installed_cli, ffmpeg, yt-dlp, and shell_execute so that
    any CLI installed by the app is always visible regardless of how the app was
    launched (e.g. from Finder vs Terminal).
    """
    parts: list[str] = []

    # Direct binary downloads (highest priority)
    parts.append(os.path.join(MUGI_DIR, "bin"))

    # Mugi Homebrew bin
    parts.append(_MUGI_HOMEBREW_BIN)

    # All CLI venv bin dirs (pip-installed programs)
    if os.path.isdir(CLI_VENVS_DIR):
        try:
            for name in sorted(os.listdir(CLI_VENVS_DIR)):
                venv_bin = os.path.join(CLI_VENVS_DIR, name, "bin")
                if os.path.isdir(venv_bin):
                    parts.append(venv_bin)
        except OSError:
            pass

    # Standard Homebrew + system locations
    parts.extend([
        "/opt/homebrew/bin",
        "/usr/local/bin",
        "/usr/bin",
        "/bin",
        "/usr/sbin",
        "/sbin",
    ])

    # Existing environment PATH (lowest priority but keeps system tools working)
    existing = os.environ.get("PATH", "")
    if existing:
        parts.append(existing)

    return os.pathsep.join(parts)


def find_cli_binary(name: str) -> str | None:
    """Find a CLI binary installed by Mugi (Homebrew or CLI venv), then system PATH.

    Resolution order:
    1. ~/.mugi/homebrew/bin/<name>
    2. ~/.mugi/cli-venvs/<name>/bin/<name>  (exact-name venv)
    3. ~/.mugi/cli-venvs/*/bin/<name>        (any CLI venv)
    4. shutil.which(name, path=cli_rich_path())
    """
    # 1. Mugi Homebrew
    brew_bin = os.path.join(_MUGI_HOMEBREW_BIN, name)
    if os.path.isfile(brew_bin):
        return brew_bin

    # 2. Exact-name CLI venv
    if os.path.isdir(CLI_VENVS_DIR):
        venv_bin = os.path.join(CLI_VENVS_DIR, name, "bin", name)
        if os.path.isfile(venv_bin):
            return venv_bin
        # Alternate: hyphen vs underscore variant
        alt = name.replace("-", "_") if "-" in name else name.replace("_", "-")
        alt_bin = os.path.join(CLI_VENVS_DIR, alt, "bin", name)
        if os.path.isfile(alt_bin):
            return alt_bin

        # 3. Any CLI venv
        try:
            for venv_name in sorted(os.listdir(CLI_VENVS_DIR)):
                candidate = os.path.join(CLI_VENVS_DIR, venv_name, "bin", name)
                if os.path.isfile(candidate):
                    return candidate
        except OSError:
            pass

    # 4. Full rich PATH
    found = shutil.which(name, path=cli_rich_path())
    return found or None


@tool
def run_installed_cli(binary_name: str, args: str = "", working_dir: str = "", max_seconds: int = 120) -> str:
    """Run a CLI program that has been installed via Mugi (Homebrew or pip install_cli_package).

    Use this for any CLI tool that the agent has installed but does not have a dedicated
    tool for. For example: exiftool, imagemagick (convert), ffprobe, pandoc, etc.

    Inputs:
    - binary_name (required): the binary to run, e.g. "convert", "exiftool", "pandoc"
    - args (optional): space-separated arguments string, e.g. "-version" or "input.png -resize 50% output.png"
    - working_dir (optional): working directory for the command (defaults to ~/Documents/Mugi)
    - max_seconds (optional): timeout in seconds (default 120, max 600)

    Safety: Only runs binaries found under ~/.mugi/homebrew, ~/.mugi/cli-venvs, or
    standard macOS system paths. Will not run arbitrary paths.

    To install new CLI programs:
    - Homebrew formula: use ensure_mugi_homebrew then mugi_brew_install('formula')
    - Python package: use install_cli_package('package-name')
    """
    _emit_input_status(f"Running {binary_name}…")
    _debug_log(f"[run_installed_cli] binary={binary_name!r} args={args!r}")

    binary_name = (binary_name or "").strip()
    if not binary_name:
        return "No binary_name provided."
    if "/" in binary_name or "\\" in binary_name:
        return f"Blocked: binary_name must be a program name, not a path (got {binary_name!r})."

    # Resolve binary
    resolved = find_cli_binary(binary_name)
    if not resolved:
        return (
            f"Binary '{binary_name}' not found in Mugi Homebrew (~/.mugi/homebrew/bin), "
            f"CLI venvs (~/.mugi/cli-venvs/*/bin), or system PATH.\n"
            f"To install via Homebrew: ensure_mugi_homebrew then mugi_brew_install('{binary_name}')\n"
            f"To install via pip: install_cli_package('{binary_name}')"
        )

    # Parse args safely
    import shlex
    try:
        arg_list = shlex.split(args) if args.strip() else []
    except ValueError as e:
        return f"Could not parse args: {e}"

    cmd = [resolved] + arg_list
    timeout = max(1, min(600, int(max_seconds or 120)))

    # Working directory
    cwd = os.path.expanduser("~/Documents/Mugi")
    if working_dir and working_dir.strip():
        wd = os.path.expanduser(working_dir.strip())
        if os.path.isdir(wd):
            cwd = wd
    os.makedirs(cwd, exist_ok=True)

    # Build safe env
    env = {
        k: v for k, v in os.environ.items()
        if k in ("HOME", "USER", "LANG", "LC_ALL", "TERM", "TMPDIR", "SHELL", "LOGNAME")
    }
    env["PATH"] = cli_rich_path()

    emit_live_view_preview(
        f"Running: {binary_name} {args[:60]}{'…' if len(args) > 60 else ''}".strip(),
        tool_name="run_installed_cli",
    )
    _debug_log(f"[run_installed_cli] Running: {' '.join(cmd)}")
    try:
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=timeout,
            cwd=cwd,
            env=env,
        )
        stdout = (result.stdout or "").strip()
        stderr = (result.stderr or "").strip()
        parts_out = [f"Command: {binary_name} {args}".strip()]
        if stdout:
            parts_out.append(f"stdout:\n{stdout}")
        if stderr:
            parts_out.append(f"stderr:\n{stderr}")
        parts_out.append(f"Return code: {result.returncode}")
        return "\n".join(parts_out)
    except subprocess.TimeoutExpired:
        return f"Command timed out after {timeout} seconds: {binary_name} {args}".strip()
    except Exception as e:
        return f"run_installed_cli failed: {e}"


@tool
def install_cli_package(package_name: str, binary_name: str = "") -> str:
    """Install a Python CLI program into its own isolated venv under ~/.mugi/cli-venvs/<name>.

    Each pip package gets a completely separate venv so its dependencies never
    affect the main agent venv (~/.mugi/venv) or any other tool. The binary is
    then available to run_installed_cli.

    Inputs:
    - package_name (required): the pip package name, e.g. "yt-dlp", "black", "httpie"
    - binary_name (optional): the binary produced by the package if it differs from package_name

    After installation, use run_installed_cli(binary_name, args) to invoke it.
    For Homebrew packages (e.g. ffmpeg, imagemagick), use ensure_mugi_homebrew + mugi_brew_install instead.
    """
    from tools.system import _load_app_prefs_tools, wait_for_shell_confirm
    from tools.cli.venvs import ensure_cli_venv, get_cli_venv_dir

    package_name = (package_name or "").strip()
    if not package_name:
        return "No package_name provided."

    prefs = _load_app_prefs_tools()
    if not prefs.get("allow_shell_execute", False):
        return "CLI package install is disabled. Enable 'Allow shell execution' in Preferences first."

    venv_dir = get_cli_venv_dir(package_name)
    display_cmd = f"pip install {package_name} → {venv_dir}"
    choice = wait_for_shell_confirm(display_cmd, use_allow_cache=True)
    if choice is None:
        return "Install confirmation timed out (no response from user)."
    if choice == "deny":
        return "Install denied by user."

    _emit_input_status(f"Installing {package_name}…")
    emit_live_view_preview(f"Installing {package_name}…", tool_name="install_cli_package", progress_pct=0)
    ok, msg = ensure_cli_venv(package_name)
    if not ok:
        return f"Installation failed: {msg}"

    bin_to_check = binary_name.strip() if binary_name else package_name
    from tools.cli.runner import find_cli_binary
    found = find_cli_binary(bin_to_check)
    emit_live_view_preview(f"Installed {package_name}.", tool_name="install_cli_package", progress_pct=100)
    if found:
        return f"Success: {package_name} installed. Binary at: {found}\n{msg}"
    return f"{msg}\nNote: binary '{bin_to_check}' not found in venv bin — the package may use a different binary name."
