"""npm/Node.js tool installation helpers."""

from __future__ import annotations

import json
import subprocess
from pathlib import Path
from typing import Any

from installer.platform_utils import command_exists
from installer.steps.deps_utils import _run_bash_with_retry


def install_mcp_cli() -> bool:
    """Install mcp-cli via bun for MCP server interaction.

    Requires bun to be installed first.
    Install command: bun install -g https://github.com/philschmid/mcp-cli
    """
    if not command_exists("bun"):
        return False

    return _run_bash_with_retry("bun install -g https://github.com/philschmid/mcp-cli")


def install_sx() -> bool:
    """Install sx (sleuth.io skills exchange) for team asset sharing."""
    if not command_exists("sx"):
        if not _run_bash_with_retry("curl -fsSL https://raw.githubusercontent.com/sleuth-io/sx/main/install.sh | bash"):
            return False

    return True


def update_sx() -> bool:
    """Update sx to the latest version."""
    if not command_exists("sx"):
        return False

    return _run_bash_with_retry("sx update")


def _is_vtsls_installed() -> bool:
    """Check if vtsls is already installed globally."""
    try:
        result = subprocess.run(
            ["npm", "list", "-g", "@vtsls/language-server"],
            capture_output=True,
            text=True,
        )
        return result.returncode == 0 and "@vtsls/language-server" in result.stdout
    except Exception:
        return False


def install_typescript_lsp() -> bool:
    """Install TypeScript language server and compiler globally."""
    if _is_vtsls_installed():
        return True
    return _run_bash_with_retry("npm install -g @vtsls/language-server typescript")


def _is_ccusage_installed() -> bool:
    """Check if ccusage is already installed globally."""
    try:
        result = subprocess.run(
            ["npm", "list", "-g", "ccusage"],
            capture_output=True,
            text=True,
        )
        return result.returncode == 0 and "ccusage" in result.stdout
    except Exception:
        return False


def install_ccusage() -> bool:
    """Install ccusage CLI for Claude Code usage tracking."""
    if _is_ccusage_installed():
        return True
    return _run_bash_with_retry("npm install -g ccusage@latest")


def _extract_npx_package_name(package: str) -> str:
    """Extract npm package name without version/tag suffix.

    Examples: "fetcher-mcp" → "fetcher-mcp",
    "open-websearch@latest" → "open-websearch",
    "@upstash/context7-mcp" → "@upstash/context7-mcp",
    "@scope/pkg@1.0" → "@scope/pkg"
    """
    if package.startswith("@"):
        parts = package[1:].split("@", 1)
        return "@" + parts[0]
    return package.split("@", 1)[0]


def _is_npx_package_cached(package: str) -> bool:
    """Check if an npx package is already cached in ~/.npm/_npx/."""
    npx_cache = Path.home() / ".npm" / "_npx"
    if not npx_cache.exists():
        return False
    pkg_name = _extract_npx_package_name(package)
    for hash_dir in npx_cache.iterdir():
        candidate = hash_dir / "node_modules" / pkg_name
        if candidate.is_dir():
            return True
    return False


def _kill_proc(proc: subprocess.Popen[Any]) -> None:
    """Terminate a process, escalating to kill if needed."""
    proc.terminate()
    try:
        proc.wait(timeout=3)
    except subprocess.TimeoutExpired:
        proc.kill()
        proc.wait(timeout=2)


def _precache_npx_mcp_servers(_ui: Any) -> bool:
    """Pre-cache npx-based MCP server packages so Claude Code can start them instantly.

    Reads .mcp.json from the plugin directory, finds servers that use npx,
    and installs each package into the npx cache using --package + -c "true".
    This ensures packages are fully installed (including all dependencies)
    before returning, avoiding the race condition of launching the actual
    server and killing it mid-install.
    """
    mcp_config_path = Path.home() / ".claude" / "tribunal" / ".mcp.json"
    if not mcp_config_path.exists():
        return True

    try:
        config = json.loads(mcp_config_path.read_text())
    except (json.JSONDecodeError, OSError):
        return False

    servers = config.get("mcpServers", {})
    npx_packages: list[str] = []

    for server_config in servers.values():
        cmd = server_config.get("command", "")
        args = server_config.get("args", [])
        if cmd == "npx" and len(args) >= 2 and args[0] == "-y":
            npx_packages.append(args[1])

    uncached = [p for p in npx_packages if not _is_npx_package_cached(p)]
    if not uncached:
        return True

    procs: list[tuple[str, subprocess.Popen[Any]]] = []
    for package in uncached:
        try:
            proc = subprocess.Popen(
                ["npx", "-y", "--package", package, "-c", "true"],
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                stdin=subprocess.DEVNULL,
            )
            procs.append((package, proc))
        except Exception:
            continue

    if not procs:
        return True

    max_wait = 120
    for _, proc in procs:
        try:
            proc.wait(timeout=max_wait)
        except subprocess.TimeoutExpired:
            _kill_proc(proc)

    return True
