"""Full environment teardown logic.

Provides detection and removal of CLI directories, Helm repos,
system dependencies (helm, kubectl, minikube, k3d, kind, docker),
and the CLI package itself.
"""

from __future__ import annotations

import contextlib
import os
import shutil
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path


@dataclass(frozen=True)
class DetectedDependency:
    """A dependency binary found on the system."""

    name: str
    path: str
    version: str
    removal_method: str  # "binary" | "apt" | "brew"


# Tools to check, in display order
_TOOL_NAMES = ("helm", "kubectl", "minikube", "k3d", "kind", "docker")

# Tools that manage clusters and need pre-removal cleanup
_CLUSTER_TOOLS: dict[str, list[list[str]]] = {
    "minikube": [["minikube", "delete", "--all", "--purge"]],
    "k3d": [["k3d", "cluster", "delete", "--all"]],
    "kind": [["kind", "delete", "clusters", "--all"]],
}

_DOCKER_APT_PACKAGES = ("docker-ce", "docker-ce-cli", "containerd.io")


def _get_version(name: str, path: str) -> str:
    """Best-effort version detection for a tool."""
    version_flags: dict[str, list[str]] = {
        "helm": [path, "version", "--short"],
        "kubectl": [path, "version", "--client", "--short"],
        "minikube": [path, "version", "--short"],
        "k3d": [path, "version"],
        "kind": [path, "version"],
        "docker": [path, "--version"],
    }
    cmd = version_flags.get(name)
    if not cmd:
        return ""
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=5)
        return result.stdout.strip().splitlines()[0] if result.stdout.strip() else ""
    except Exception:  # noqa: BLE001
        return ""


def _detect_removal_method(name: str) -> str:
    """Detect the best removal method for a tool."""
    if name == "docker":
        # Check if installed via apt
        if shutil.which("dpkg"):
            try:
                result = subprocess.run(
                    ["dpkg", "-s", "docker-ce"],
                    capture_output=True,
                    text=True,
                    timeout=5,
                )
                if result.returncode == 0:
                    return "apt"
            except Exception:  # noqa: BLE001
                pass
        if shutil.which("brew"):
            return "brew"
    return "binary"


def detect_dependencies() -> list[DetectedDependency]:
    """Detect installed Ilum-related dependencies.

    Checks for: helm, kubectl, minikube, k3d, kind, docker.
    Returns only tools that are actually installed.
    """
    found: list[DetectedDependency] = []
    for name in _TOOL_NAMES:
        path = shutil.which(name)
        if path is None:
            continue
        found.append(
            DetectedDependency(
                name=name,
                path=path,
                version=_get_version(name, path),
                removal_method=_detect_removal_method(name),
            )
        )
    return found


def remove_cli_directories(dirs: list[Path], *, dry_run: bool = False) -> list[Path]:
    """Remove CLI config/state/cache directories.

    Returns list of directories that were (or would be) removed.
    Skips directories that don't exist.
    """
    removed: list[Path] = []
    for d in dirs:
        if not d.exists():
            continue
        removed.append(d)
        if not dry_run:
            shutil.rmtree(d)
    return removed


def remove_dependency(dep: DetectedDependency, *, dry_run: bool = False) -> bool:
    """Remove a single dependency.

    For cluster providers (minikube, k3d, kind): deletes all clusters first.
    For docker: uses apt or brew depending on install method.
    For plain binaries: removes the file directly.

    Returns True if removal succeeded (or would succeed in dry-run).
    """
    if dry_run:
        return True

    # Pre-removal: clean up clusters for provider tools
    pre_cmds = _CLUSTER_TOOLS.get(dep.name, [])
    for cmd in pre_cmds:
        with contextlib.suppress(Exception):
            subprocess.run(cmd, capture_output=True, text=True, timeout=120)

    # Docker uses package manager
    if dep.name == "docker":
        return _remove_docker(dep)

    # Binary removal — try direct first, fall back to sudo
    try:
        os.remove(dep.path)
        return True
    except PermissionError:
        try:
            subprocess.run(
                ["sudo", "rm", dep.path],
                capture_output=True,
                text=True,
                timeout=30,
                check=True,
            )
            return True
        except Exception:  # noqa: BLE001
            return False
    except OSError:
        return False


def _remove_docker(dep: DetectedDependency) -> bool:
    """Remove Docker via the detected package manager."""
    if dep.removal_method == "apt":
        try:
            subprocess.run(
                ["sudo", "apt", "remove", "-y", *_DOCKER_APT_PACKAGES],
                capture_output=True,
                text=True,
                timeout=120,
                check=True,
            )
            return True
        except Exception:  # noqa: BLE001
            return False
    if dep.removal_method == "brew":
        try:
            subprocess.run(
                ["brew", "uninstall", "docker"],
                capture_output=True,
                text=True,
                timeout=120,
                check=True,
            )
            return True
        except Exception:  # noqa: BLE001
            return False
    # Fallback: try removing binary
    try:
        os.remove(dep.path)
        return True
    except OSError:
        return False


def docker_has_running_containers() -> bool:
    """Check if Docker has running containers."""
    if shutil.which("docker") is None:
        return False
    try:
        result = subprocess.run(
            ["docker", "ps", "-q"],
            capture_output=True,
            text=True,
            timeout=10,
        )
        return bool(result.stdout.strip())
    except Exception:  # noqa: BLE001
        return False


def remove_helm_repo(*, dry_run: bool = False) -> bool:
    """Remove the 'ilum' Helm repository.

    Returns True if removed, False if repo didn't exist or helm not found.
    """
    if shutil.which("helm") is None:
        return False
    if dry_run:
        return True
    try:
        result = subprocess.run(
            ["helm", "repo", "remove", "ilum"],
            capture_output=True,
            text=True,
            timeout=30,
        )
        return result.returncode == 0
    except Exception:  # noqa: BLE001
        return False


def uninstall_cli(*, dry_run: bool = False) -> tuple[bool, str]:
    """Uninstall the ilum CLI package.

    Tries in order: frozen binary → pipx → uv tool → sys.executable -m pip.
    Returns (success, method_description).
    """
    if dry_run:
        return (True, "dry-run")

    # Standalone binary (PyInstaller): delete the executable directly
    if getattr(sys, "frozen", False):
        exe = sys.executable
        try:
            os.remove(exe)
            return (True, f"Removed binary {exe}")
        except PermissionError:
            try:
                subprocess.run(
                    ["sudo", "rm", exe],
                    capture_output=True,
                    text=True,
                    timeout=30,
                    check=True,
                )
                return (True, f"Removed binary {exe} (via sudo)")
            except Exception:  # noqa: BLE001
                return (False, f"Remove the binary manually: sudo rm {exe}")
        except OSError:
            return (False, f"Remove the binary manually: rm {exe}")

    installers: list[tuple[list[str], str]] = [
        (["pipx", "uninstall", "ilum"], "pipx"),
        (["uv", "tool", "uninstall", "ilum"], "uv"),
        ([sys.executable, "-m", "pip", "uninstall", "-y", "ilum"], "pip"),
    ]
    for cmd, label in installers:
        if label in ("pipx", "uv") and shutil.which(cmd[0]) is None:
            continue
        try:
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                timeout=60,
            )
            if result.returncode == 0:
                return (True, f"Uninstalled via {label}")
        except Exception:  # noqa: BLE001
            continue
    return (False, f"Try: {sys.executable} -m pip uninstall ilum")


def count_files_in_dir(d: Path) -> int:
    """Count files in a directory (non-recursive, for preview display)."""
    if not d.exists():
        return 0
    try:
        return sum(1 for _ in d.iterdir())
    except OSError:
        return 0
