"""Utility for downloading and installing external scanner binaries."""

from __future__ import annotations

import logging
import os
import platform
import shutil
import tarfile
import tempfile
import zipfile
from pathlib import Path

import httpx
from rich.console import Console
from rich.progress import Progress, SpinnerColumn, TextColumn

logger = logging.getLogger(__name__)
console = Console()

# Where to install downloaded scanners (~/.secagent/bin)
_INSTALL_DIR = Path.home() / ".secagent" / "bin"

# GitHub API for latest releases
_GITHUB_API = "https://api.github.com/repos/{owner}/{repo}/releases/latest"


def get_binary_path(name: str) -> Path | None:
    """Return the path to the executable if it exists, otherwise ``None``."""
    # 1. Try system PATH
    system_path = shutil.which(name)
    if system_path:
        return Path(system_path)

    # 2. Try our private bin dir
    exe_name = f"{name}.exe" if platform.system() == "Windows" else name
    private_path = _INSTALL_DIR / exe_name
    if private_path.exists():
        return private_path

    return None


def _get_latest_release_url(owner: str, repo: str, name: str) -> str | None:
    """Fetch the latest release from GitHub and find the right asset for this OS/arch."""
    system = platform.system().lower()
    machine = platform.machine().lower()

    # Build search terms for matching asset names
    if system == "windows":
        os_terms = ["windows"]
        ext = ".zip"
    elif system == "darwin":
        os_terms = ["darwin", "macos", "macOS"]
        ext = ".tar.gz"
    else:
        os_terms = ["linux"]
        ext = ".tar.gz"

    if machine in ("amd64", "x86_64"):
        arch_terms = ["x64", "64bit", "amd64", "x86_64"]
    elif machine in ("aarch64", "arm64"):
        arch_terms = ["arm64", "ARM64", "aarch64"]
    else:
        arch_terms = ["x64", "64bit"]

    try:
        url = _GITHUB_API.format(owner=owner, repo=repo)
        resp = httpx.get(url, timeout=15, follow_redirects=True)
        resp.raise_for_status()
        data = resp.json()

        assets = data.get("assets", [])
        for asset in assets:
            asset_name: str = asset.get("name", "")
            download_url: str = asset.get("browser_download_url", "")

            # Must end with the right extension
            if not asset_name.endswith(ext):
                continue
            # Skip sigstore/checksum files
            if "sigstore" in asset_name or "checksums" in asset_name:
                continue

            name_lower = asset_name.lower()
            # Must match OS
            if not any(t.lower() in name_lower for t in os_terms):
                continue
            # Must match arch
            if not any(t.lower() in name_lower for t in arch_terms):
                continue

            logger.info("Found asset: %s", asset_name)
            return download_url

        logger.warning("No matching asset found for %s on %s/%s", name, system, machine)
        return None

    except Exception as exc:
        logger.error("Failed to fetch latest release for %s/%s: %s", owner, repo, exc)
        return None


def ensure_installed(name: str, owner: str, repo: str) -> Path | None:
    """Check if *name* is installed; if not, download latest from GitHub."""
    existing = get_binary_path(name)
    if existing:
        return existing

    console.print(f"[yellow]Scanner '{name}' is not installed.[/]")

    download_url = _get_latest_release_url(owner, repo, name)
    if not download_url:
        console.print(f"[red]Could not find a download for '{name}' on your platform.[/]")
        return None

    console.print(f"[cyan]Downloading {name} from GitHub…[/]")

    _INSTALL_DIR.mkdir(parents=True, exist_ok=True)
    exe_name = f"{name}.exe" if platform.system() == "Windows" else name
    target_path = _INSTALL_DIR / exe_name

    try:
        with tempfile.NamedTemporaryFile(delete=False) as tmp:
            tmp_path = Path(tmp.name)

        # Download with progress
        with Progress(
            SpinnerColumn(),
            TextColumn("[progress.description]{task.description}"),
            console=console,
        ) as progress:
            task = progress.add_task(f"Downloading {name}…", total=None)
            with httpx.stream("GET", download_url, follow_redirects=True, timeout=120) as response:
                response.raise_for_status()
                with open(tmp_path, "wb") as f:
                    for chunk in response.iter_bytes(chunk_size=8192):
                        f.write(chunk)
            progress.update(task, completed=True)

        # Extract the executable
        if download_url.endswith(".zip"):
            _extract_zip(tmp_path, exe_name, target_path)
        elif download_url.endswith(".tar.gz") or download_url.endswith(".tgz"):
            _extract_tar(tmp_path, exe_name, target_path)
        else:
            shutil.copy(tmp_path, target_path)

        # Make executable (Linux/macOS)
        if platform.system() != "Windows":
            target_path.chmod(0o755)

        console.print(f"[green]✅ Successfully installed {name} → {target_path}[/]\n")
        return target_path

    except Exception as exc:
        logger.error("Failed to install %s: %s", name, exc)
        console.print(f"[red]Failed to install {name}: {exc}[/]\n")
        return None
    finally:
        if "tmp_path" in locals() and tmp_path.exists():
            tmp_path.unlink(missing_ok=True)


def _extract_zip(archive: Path, exe_name: str, target: Path) -> None:
    """Extract the binary from a zip archive."""
    with zipfile.ZipFile(archive, "r") as z:
        for member in z.namelist():
            if member.endswith(exe_name) or os.path.basename(member) == exe_name:
                with z.open(member) as source, open(target, "wb") as dest:
                    shutil.copyfileobj(source, dest)
                return
    raise FileNotFoundError(f"Could not find '{exe_name}' in zip archive")


def _extract_tar(archive: Path, exe_name: str, target: Path) -> None:
    """Extract the binary from a tar.gz archive."""
    with tarfile.open(archive, "r:gz") as t:
        for member in t.getmembers():
            if member.name.endswith(exe_name) or os.path.basename(member.name) == exe_name:
                f = t.extractfile(member)
                if f is not None:
                    with open(target, "wb") as dest:
                        shutil.copyfileobj(f, dest)
                    return
    raise FileNotFoundError(f"Could not find '{exe_name}' in tar archive")
