"""Individual health checks for ``ilum doctor``."""

from __future__ import annotations

import shutil
import subprocess
from dataclasses import dataclass
from enum import Enum
from typing import TYPE_CHECKING

from ilum.constants import MIN_DOCKER_VERSION, MIN_HELM_VERSION, MIN_KUBECTL_VERSION
from ilum.core.versioning import parse_version

if TYPE_CHECKING:
    from ilum.core.helm import HelmClient
    from ilum.core.kubernetes import KubeClient


class CheckStatus(Enum):
    OK = "ok"
    WARN = "warn"
    FAIL = "fail"
    SKIP = "skip"


@dataclass
class CheckResult:
    name: str
    status: CheckStatus
    message: str
    suggestion: str = ""
    fixable: bool = False


def _parse_version(raw: str) -> tuple[int, ...]:
    """Extract the first semver-like version from a string."""
    return parse_version(raw)


def check_binary(name: str, min_version: tuple[int, int] | None = None) -> CheckResult:
    """Check that a binary exists on PATH and optionally meets a minimum version."""
    path = shutil.which(name)
    if path is None:
        return CheckResult(
            name=name,
            status=CheckStatus.FAIL,
            message=f"{name} not found on PATH",
            suggestion=f"Install {name}: https://kubernetes.io/docs/tasks/tools/",
        )

    if min_version is None:
        return CheckResult(name=name, status=CheckStatus.OK, message=f"{name} found at {path}")

    try:
        if name == "helm":
            version_cmd = [name, "version", "--short"]
        elif name == "kubectl":
            version_cmd = [name, "version", "--client", "-o", "json"]
        else:
            version_cmd = [name, "--version"]
        result = subprocess.run(
            version_cmd,
            capture_output=True,
            text=True,
            timeout=10,
        )
        raw = result.stdout + result.stderr
    except (subprocess.TimeoutExpired, OSError):
        return CheckResult(
            name=name,
            status=CheckStatus.WARN,
            message=f"{name} found but could not determine version",
        )

    ver = _parse_version(raw)
    if (ver[0], ver[1]) < min_version:
        return CheckResult(
            name=name,
            status=CheckStatus.FAIL,
            message=(
                f"{name} version {ver[0]}.{ver[1]}.{ver[2]}"
                f" < required {min_version[0]}.{min_version[1]}"
            ),
            suggestion=f"Upgrade {name} to at least {min_version[0]}.{min_version[1]}",
        )

    return CheckResult(
        name=name,
        status=CheckStatus.OK,
        message=f"{name} {ver[0]}.{ver[1]}.{ver[2]}",
    )


def check_cluster_connectivity(k8s: KubeClient) -> CheckResult:
    if not k8s.is_connected():
        return CheckResult(
            name="cluster",
            status=CheckStatus.FAIL,
            message="Cannot connect to Kubernetes cluster",
            suggestion="Check your kubeconfig: kubectl cluster-info",
        )
    version = k8s.get_server_version()
    return CheckResult(
        name="cluster",
        status=CheckStatus.OK,
        message=f"Connected to Kubernetes {version}",
    )


def check_namespace(k8s: KubeClient, namespace: str) -> CheckResult:
    if not k8s.namespace_exists(namespace):
        return CheckResult(
            name="namespace",
            status=CheckStatus.FAIL,
            message=f"Namespace '{namespace}' does not exist",
            suggestion=f"Create it: kubectl create namespace {namespace}",
            fixable=True,
        )
    return CheckResult(
        name="namespace",
        status=CheckStatus.OK,
        message=f"Namespace '{namespace}' exists",
    )


def check_pod_health(k8s: KubeClient, namespace: str) -> CheckResult:
    unhealthy = k8s.get_pod_health(namespace)
    if not unhealthy:
        return CheckResult(
            name="pods",
            status=CheckStatus.OK,
            message="All pods are healthy",
        )

    crashloop = [p for p in unhealthy if p.restart_count > 5]
    if crashloop:
        names = ", ".join(p.name for p in crashloop[:3])
        return CheckResult(
            name="pods",
            status=CheckStatus.FAIL,
            message=f"CrashLoopBackOff detected: {names}",
            suggestion="Check pod logs: kubectl logs <pod-name>",
        )

    names = ", ".join(p.name for p in unhealthy[:3])
    suffix = f" (+{len(unhealthy) - 3} more)" if len(unhealthy) > 3 else ""
    return CheckResult(
        name="pods",
        status=CheckStatus.WARN,
        message=f"Unhealthy pods: {names}{suffix}",
        suggestion="Check pod status: kubectl get pods",
    )


def check_pvc_status(k8s: KubeClient, namespace: str) -> CheckResult:
    pvcs = k8s.list_pvcs(namespace)
    pending = [p for p in pvcs if p.phase == "Pending"]
    if not pending:
        return CheckResult(
            name="pvcs",
            status=CheckStatus.OK,
            message=f"{len(pvcs)} PVCs all bound" if pvcs else "No PVCs found",
        )
    names = ", ".join(p.name for p in pending[:3])
    return CheckResult(
        name="pvcs",
        status=CheckStatus.WARN,
        message=f"Pending PVCs: {names}",
        suggestion="Check storage provisioner: kubectl describe pvc",
    )


def check_rbac_permissions(k8s: KubeClient, namespace: str) -> CheckResult:
    if k8s.check_rbac(namespace):
        return CheckResult(
            name="rbac",
            status=CheckStatus.OK,
            message="Required RBAC permissions granted",
        )
    return CheckResult(
        name="rbac",
        status=CheckStatus.WARN,
        message="Some RBAC permissions may be missing",
        suggestion="Ensure your user has cluster-admin or equivalent role",
    )


def check_helm_release(helm: HelmClient, release: str) -> CheckResult:
    try:
        result = helm.history(release)
    except Exception:
        return CheckResult(
            name="release",
            status=CheckStatus.WARN,
            message=f"Release '{release}' not found",
            suggestion="Install with: ilum install",
        )

    if not result.json_data:
        return CheckResult(
            name="release",
            status=CheckStatus.WARN,
            message=f"No history for release '{release}'",
        )

    latest = result.json_data[-1]
    status = latest.get("status", "unknown")
    if status in ("pending-install", "pending-upgrade", "pending-rollback"):
        return CheckResult(
            name="release",
            status=CheckStatus.FAIL,
            message=f"Release '{release}' stuck in {status}",
            suggestion=f"Rollback: helm rollback {release}",
        )
    if status == "failed":
        return CheckResult(
            name="release",
            status=CheckStatus.FAIL,
            message=f"Release '{release}' is in failed state",
            suggestion=f"Check: helm history {release}",
        )
    return CheckResult(
        name="release",
        status=CheckStatus.OK,
        message=f"Release '{release}' status: {status}",
    )


def check_helm_repo(helm: HelmClient) -> CheckResult:
    try:
        result = helm._run(["repo", "list", "--output", "json"], capture_json=True)
    except Exception:
        return CheckResult(
            name="helm-repo",
            status=CheckStatus.FAIL,
            message="No Helm repos configured",
            suggestion="Add repo: helm repo add ilum https://charts.ilum.cloud",
        )

    if result.json_data:
        for repo in result.json_data:
            if repo.get("name") == "ilum" or "ilum.cloud" in repo.get("url", ""):
                return CheckResult(
                    name="helm-repo",
                    status=CheckStatus.OK,
                    message="ilum Helm repo configured",
                )
    return CheckResult(
        name="helm-repo",
        status=CheckStatus.WARN,
        message="ilum Helm repo not found",
        suggestion="Add repo: helm repo add ilum https://charts.ilum.cloud",
        fixable=True,
    )


def check_version_compatibility(helm: HelmClient, k8s: KubeClient) -> CheckResult:
    try:
        k8s_ver = k8s.get_server_version()
        k8s_parts = _parse_version(k8s_ver)
    except Exception:
        return CheckResult(
            name="compatibility",
            status=CheckStatus.SKIP,
            message="Cannot determine Kubernetes version",
        )

    if (k8s_parts[0], k8s_parts[1]) < (1, 28):
        return CheckResult(
            name="compatibility",
            status=CheckStatus.WARN,
            message=f"Kubernetes {k8s_ver} may not be fully supported (1.28+ recommended)",
            suggestion="Consider upgrading your Kubernetes cluster",
        )

    return CheckResult(
        name="compatibility",
        status=CheckStatus.OK,
        message=f"Kubernetes {k8s_ver} is compatible",
    )


def check_storage_class(k8s: KubeClient) -> CheckResult:
    """Warn if no default StorageClass is available."""
    try:
        classes = k8s.list_storage_classes()
        if not classes:
            return CheckResult(
                name="storage-class",
                status=CheckStatus.WARN,
                message="No StorageClasses found",
                suggestion="Install a storage provisioner for persistent volumes.",
            )
        defaults = [sc for sc in classes if sc.get("is_default")]
        if not defaults:
            names = ", ".join(sc["name"] for sc in classes[:3])
            return CheckResult(
                name="storage-class",
                status=CheckStatus.WARN,
                message=f"No default StorageClass (available: {names})",
                suggestion=(
                    "Mark a StorageClass as default: kubectl patch storageclass <name>"
                    ' -p \'{"metadata": {"annotations":'
                    '{"storageclass.kubernetes.io/is-default-class":"true"}}}\''
                ),
            )
        default_name = defaults[0]["name"]
        return CheckResult(
            name="storage-class",
            status=CheckStatus.OK,
            message=f"Default StorageClass: {default_name}",
        )
    except Exception:
        return CheckResult(
            name="storage-class",
            status=CheckStatus.SKIP,
            message="Cannot query StorageClasses",
        )


def check_resources(k8s: KubeClient, modules: list[str]) -> CheckResult:
    """Check if cluster has sufficient resources for the given modules."""
    try:
        from ilum.core.resources import check_resources_for_modules

        # Try to get cluster resources
        try:
            cluster_resources = k8s.get_cluster_resources()
        except Exception:
            cluster_resources = None

        result = check_resources_for_modules(modules, cluster_resources)

        if result.sufficient:
            return CheckResult(
                name="resources",
                status=CheckStatus.OK,
                message=result.message,
            )
        else:
            return CheckResult(
                name="resources",
                status=CheckStatus.WARN,
                message=result.message,
                suggestion="Consider disabling non-essential modules or adding cluster resources.",
            )
    except Exception:
        return CheckResult(
            name="resources",
            status=CheckStatus.SKIP,
            message="Cannot estimate resource requirements",
        )


def check_service_endpoints(k8s: KubeClient, namespace: str) -> CheckResult:
    """Verify that core services have at least one ready endpoint."""
    from ilum.constants import HEALTH_ENDPOINTS

    missing: list[str] = []
    checked = 0
    for svc_name in HEALTH_ENDPOINTS:
        try:
            if not k8s.list_service_endpoints(namespace, svc_name):
                missing.append(svc_name)
            checked += 1
        except Exception:
            pass  # Best-effort — skip if we can't check

    if not checked:
        return CheckResult(
            name="service-endpoints",
            status=CheckStatus.SKIP,
            message="Could not verify any service endpoints",
        )
    if missing:
        names = ", ".join(missing[:3])
        return CheckResult(
            name="service-endpoints",
            status=CheckStatus.WARN,
            message=f"Services without ready endpoints: {names}",
            suggestion="Check pod health and service selectors.",
        )
    return CheckResult(
        name="service-endpoints",
        status=CheckStatus.OK,
        message=f"{checked} services have ready endpoints",
    )


def check_health_endpoints(k8s: KubeClient, namespace: str) -> CheckResult:
    """Validate health endpoints for known services via pod proxy."""
    from ilum.constants import HEALTH_ENDPOINTS

    # Map service names to their health port based on CLAUDE.md docs
    health_ports: dict[str, int] = {
        "ilum-core": 9888,
        "airflow": 8080,
        "superset": 8088,
        "minio": 9000,
    }

    healthy: list[str] = []
    unhealthy: list[str] = []

    for svc_name, path in HEALTH_ENDPOINTS.items():
        port = health_ports.get(svc_name, 8080)
        # Find a running pod for this service
        try:
            pods = k8s.list_pods_by_label(namespace, f"app={svc_name}")
            running = [p for p in pods if p.phase == "Running" and p.ready]
            if not running:
                continue  # Service not deployed — skip

            ok, msg = k8s.check_health_endpoint(namespace, running[0].name, port, path)
            if ok:
                healthy.append(svc_name)
            else:
                unhealthy.append(f"{svc_name}: {msg}")
        except Exception:
            pass  # Best-effort

    if not healthy and not unhealthy:
        return CheckResult(
            name="health-endpoints",
            status=CheckStatus.SKIP,
            message="No services available for health check",
        )
    if unhealthy:
        msgs = "; ".join(unhealthy[:3])
        return CheckResult(
            name="health-endpoints",
            status=CheckStatus.WARN,
            message=f"Unhealthy endpoints: {msgs}",
            suggestion="Check service logs for errors.",
        )
    return CheckResult(
        name="health-endpoints",
        status=CheckStatus.OK,
        message=f"{len(healthy)} health endpoints responding",
    )


def get_all_binary_checks() -> list[CheckResult]:
    return [
        check_binary("helm", MIN_HELM_VERSION),
        check_binary("kubectl", MIN_KUBECTL_VERSION),
        check_binary("docker", MIN_DOCKER_VERSION),
    ]
