"""Install and update external development tools at pinned versions."""

from __future__ import annotations

import platform
import shutil
import subprocess
import tarfile
import tempfile
from io import BytesIO
from pathlib import Path

import click
import httpx

# ---------------------------------------------------------------------------
# Version lock file parser
# ---------------------------------------------------------------------------

def parse_versions_lock(path: Path) -> dict[str, str]:
    """Parse a versions.lock file into {TOOL_NAME: version} dict."""
    versions: dict[str, str] = {}
    for line in path.read_text().splitlines():
        line = line.strip()
        if not line or line.startswith("#"):
            continue
        if "=" in line:
            key, _, value = line.partition("=")
            versions[key.strip()] = value.strip()
    return versions


# ---------------------------------------------------------------------------
# Version detection helpers
# ---------------------------------------------------------------------------

def _get_installed_version(cmd: str, version_flag: str = "--version") -> str | None:
    """Get the installed version of a tool, or None if not installed."""
    exe = shutil.which(cmd)
    if not exe:
        return None
    try:
        result = subprocess.run(
            [exe, version_flag], capture_output=True, text=True, timeout=10
        )
        output = result.stdout.strip() or result.stderr.strip()
        # Extract version number from output like "gitleaks v8.29.0" or "v0.43.5"
        for token in output.replace(",", " ").split():
            cleaned = token.lstrip("v").strip()
            if cleaned and cleaned[0].isdigit():
                return cleaned
        return output
    except (subprocess.TimeoutExpired, FileNotFoundError):
        return None


def _detect_arch() -> str:
    """Detect CPU architecture for binary downloads."""
    machine = platform.machine().lower()
    if machine in ("x86_64", "amd64"):
        return "x86_64"
    if machine in ("aarch64", "arm64"):
        return "arm64"
    return machine


def _detect_os() -> str:
    """Detect OS for binary downloads."""
    system = platform.system().lower()
    if system == "darwin":
        return "darwin"
    return "linux"


# ---------------------------------------------------------------------------
# Individual tool installers
# ---------------------------------------------------------------------------

def _install_dir() -> Path:
    """Return the user-local bin directory, creating it if needed."""
    bin_dir = Path.home() / ".local" / "bin"
    bin_dir.mkdir(parents=True, exist_ok=True)
    return bin_dir


def install_gitleaks(version: str, dry_run: bool = False) -> bool:
    """Install gitleaks from GitHub release."""
    current = _get_installed_version("gitleaks")
    if current == version:
        click.echo(f"  gitleaks {version} already installed")
        return True

    os_name = _detect_os()
    arch = _detect_arch()
    asset = f"gitleaks_{version}_{os_name}_{arch}.tar.gz"
    url = f"https://github.com/gitleaks/gitleaks/releases/download/v{version}/{asset}"

    if dry_run:
        click.echo(f"  [dry-run] Would install gitleaks {version} from {url}")
        return True

    click.echo(f"  Installing gitleaks {version}...")
    return _download_and_extract_binary(url, "gitleaks", _install_dir())


def install_squawk(version: str, dry_run: bool = False) -> bool:
    """Install squawk from GitHub release."""
    current = _get_installed_version("squawk")
    if current == version:
        click.echo(f"  squawk {version} already installed")
        return True

    os_name = _detect_os()
    arch = _detect_arch()
    # squawk uses different naming: squawk-linux-x86_64.tar.gz
    os_label = "Linux" if os_name == "linux" else "Darwin"
    arch_label = "x86_64" if arch == "x86_64" else "aarch64"
    asset = f"squawk-{os_label}-{arch_label}.tar.gz"
    url = f"https://github.com/sbdchd/squawk/releases/download/v{version}/{asset}"

    if dry_run:
        click.echo(f"  [dry-run] Would install squawk {version} from {url}")
        return True

    click.echo(f"  Installing squawk {version}...")
    return _download_and_extract_binary(url, "squawk", _install_dir())


def install_elspais(version: str, dry_run: bool = False) -> bool:
    """Install elspais via pip."""
    current = _get_installed_version("elspais")
    if current == version:
        click.echo(f"  elspais {version} already installed")
        return True

    if dry_run:
        click.echo(f"  [dry-run] Would pip install elspais=={version}")
        return True

    click.echo(f"  Installing elspais {version}...")
    result = subprocess.run(
        ["pip", "install", f"elspais=={version}"],
        capture_output=True, text=True,
    )
    if result.returncode != 0:
        click.echo(f"  ERROR installing elspais: {result.stderr}", err=True)
        return False
    click.echo(f"  elspais {version} installed")
    return True


def install_markdownlint(version: str, dry_run: bool = False) -> bool:
    """Install markdownlint-cli via npm."""
    current = _get_installed_version("markdownlint", "--version")
    if current == version:
        click.echo(f"  markdownlint-cli {version} already installed")
        return True

    if dry_run:
        click.echo(f"  [dry-run] Would npm install -g markdownlint-cli@{version}")
        return True

    click.echo(f"  Installing markdownlint-cli {version}...")
    result = subprocess.run(
        ["npm", "install", "-g", f"markdownlint-cli@{version}"],
        capture_output=True, text=True,
    )
    if result.returncode != 0:
        click.echo(f"  ERROR installing markdownlint-cli: {result.stderr}", err=True)
        return False
    click.echo(f"  markdownlint-cli {version} installed")
    return True


# ---------------------------------------------------------------------------
# Binary download helper
# ---------------------------------------------------------------------------

def _download_and_extract_binary(url: str, binary_name: str, dest_dir: Path) -> bool:
    """Download a tar.gz, extract the named binary, and place it in dest_dir."""
    try:
        with httpx.Client(follow_redirects=True, timeout=60) as client:
            resp = client.get(url)
            resp.raise_for_status()

        with tempfile.TemporaryDirectory() as tmp:
            tmp_path = Path(tmp)
            with tarfile.open(fileobj=BytesIO(resp.content), mode="r:gz") as tar:
                tar.extractall(tmp_path, filter="data")

            # Find the binary in extracted files
            candidates = list(tmp_path.rglob(binary_name))
            if not candidates:
                click.echo(f"  ERROR: {binary_name} not found in archive", err=True)
                return False

            binary_src = candidates[0]
            binary_dest = dest_dir / binary_name
            shutil.copy2(binary_src, binary_dest)
            binary_dest.chmod(0o755)
            click.echo(f"  {binary_name} installed to {binary_dest}")
            return True

    except httpx.HTTPError as e:
        click.echo(f"  ERROR downloading {url}: {e}", err=True)
        return False


# ---------------------------------------------------------------------------
# Main entry point
# ---------------------------------------------------------------------------

TOOL_INSTALLERS = {
    "GITLEAKS_VERSION": ("gitleaks", install_gitleaks),
    "SQUAWK_VERSION": ("squawk", install_squawk),
    "ELSPAIS_VERSION": ("elspais", install_elspais),
    "MARKDOWNLINT_CLI_VERSION": ("markdownlint-cli", install_markdownlint),
}


def install_all_tools(versions_lock: Path, dry_run: bool = False) -> bool:
    """Install all tools from versions.lock. Returns True if all succeeded."""
    versions = parse_versions_lock(versions_lock)
    click.echo(f"Reading versions from {versions_lock}")

    all_ok = True
    for key, (name, installer) in TOOL_INSTALLERS.items():
        version = versions.get(key)
        if not version:
            click.echo(f"  WARN: {key} not found in versions.lock, skipping {name}")
            continue
        if not installer(version, dry_run=dry_run):
            all_ok = False

    if all_ok:
        click.echo("All tools up to date.")
    else:
        click.echo("Some tools failed to install.", err=True)
    return all_ok
