"""Tests for the doctor checks and runner."""

from __future__ import annotations

from unittest.mock import MagicMock, patch

from ilum.cli.output import IlumConsole
from ilum.core.helm import HelmResult
from ilum.core.kubernetes import PodStatus, PVCStatus
from ilum.doctor.checks import (
    CheckStatus,
    check_binary,
    check_cluster_connectivity,
    check_health_endpoints,
    check_helm_release,
    check_namespace,
    check_pod_health,
    check_pvc_status,
    check_service_endpoints,
)
from ilum.doctor.runner import DoctorRunner


class TestCheckBinary:
    @patch("shutil.which", return_value="/usr/bin/helm")
    @patch("subprocess.run")
    def test_found_good_version(self, mock_run: MagicMock, mock_which: MagicMock) -> None:
        mock_run.return_value = MagicMock(stdout="v3.14.0", stderr="", returncode=0)
        result = check_binary("helm", (3, 12))
        assert result.status == CheckStatus.OK

    @patch("shutil.which", return_value=None)
    def test_not_found(self, mock_which: MagicMock) -> None:
        result = check_binary("helm", (3, 12))
        assert result.status == CheckStatus.FAIL
        assert "not found" in result.message

    @patch("shutil.which", return_value="/usr/bin/helm")
    @patch("subprocess.run")
    def test_wrong_version(self, mock_run: MagicMock, mock_which: MagicMock) -> None:
        mock_run.return_value = MagicMock(stdout="v3.10.0", stderr="", returncode=0)
        result = check_binary("helm", (3, 12))
        assert result.status == CheckStatus.FAIL
        assert "3.10" in result.message

    @patch("shutil.which", return_value="/usr/bin/helm")
    def test_found_no_version_check(self, mock_which: MagicMock) -> None:
        result = check_binary("helm")
        assert result.status == CheckStatus.OK


class TestClusterConnectivity:
    def test_connected(self, mock_k8s: MagicMock) -> None:
        result = check_cluster_connectivity(mock_k8s)
        assert result.status == CheckStatus.OK
        assert "1.30.0" in result.message

    def test_disconnected(self, mock_k8s: MagicMock) -> None:
        mock_k8s.is_connected.return_value = False
        result = check_cluster_connectivity(mock_k8s)
        assert result.status == CheckStatus.FAIL


class TestNamespaceCheck:
    def test_exists(self, mock_k8s: MagicMock) -> None:
        result = check_namespace(mock_k8s, "default")
        assert result.status == CheckStatus.OK

    def test_missing(self, mock_k8s: MagicMock) -> None:
        mock_k8s.namespace_exists.return_value = False
        result = check_namespace(mock_k8s, "missing")
        assert result.status == CheckStatus.FAIL
        assert "missing" in result.message


class TestPodHealth:
    def test_all_healthy(self, mock_k8s: MagicMock) -> None:
        mock_k8s.get_pod_health.return_value = []
        result = check_pod_health(mock_k8s, "default")
        assert result.status == CheckStatus.OK

    def test_crashloop_detected(self, mock_k8s: MagicMock) -> None:
        mock_k8s.get_pod_health.return_value = [
            PodStatus(
                name="bad-pod",
                namespace="default",
                phase="Running",
                ready=False,
                restart_count=10,
            ),
        ]
        result = check_pod_health(mock_k8s, "default")
        assert result.status == CheckStatus.FAIL
        assert "CrashLoopBackOff" in result.message

    def test_unhealthy_warning(self, mock_k8s: MagicMock) -> None:
        mock_k8s.get_pod_health.return_value = [
            PodStatus(
                name="slow-pod",
                namespace="default",
                phase="Pending",
                ready=False,
                restart_count=0,
            ),
        ]
        result = check_pod_health(mock_k8s, "default")
        assert result.status == CheckStatus.WARN


class TestPVCStatus:
    def test_all_bound(self, mock_k8s: MagicMock) -> None:
        mock_k8s.list_pvcs.return_value = [
            PVCStatus(name="data", namespace="default", phase="Bound"),
        ]
        result = check_pvc_status(mock_k8s, "default")
        assert result.status == CheckStatus.OK

    def test_pending_pvcs(self, mock_k8s: MagicMock) -> None:
        mock_k8s.list_pvcs.return_value = [
            PVCStatus(name="stuck", namespace="default", phase="Pending"),
        ]
        result = check_pvc_status(mock_k8s, "default")
        assert result.status == CheckStatus.WARN


class TestHelmRelease:
    def test_healthy_release(self, mock_helm: MagicMock) -> None:
        mock_helm.history.return_value = HelmResult(
            returncode=0,
            stdout="",
            stderr="",
            json_data=[{"status": "deployed"}],
        )
        result = check_helm_release(mock_helm, "ilum")
        assert result.status == CheckStatus.OK

    def test_stuck_pending_install(self, mock_helm: MagicMock) -> None:
        mock_helm.history.return_value = HelmResult(
            returncode=0,
            stdout="",
            stderr="",
            json_data=[{"status": "pending-install"}],
        )
        result = check_helm_release(mock_helm, "ilum")
        assert result.status == CheckStatus.FAIL
        assert "stuck" in result.message

    def test_failed_release(self, mock_helm: MagicMock) -> None:
        mock_helm.history.return_value = HelmResult(
            returncode=0,
            stdout="",
            stderr="",
            json_data=[{"status": "failed"}],
        )
        result = check_helm_release(mock_helm, "ilum")
        assert result.status == CheckStatus.FAIL

    def test_release_not_found(self, mock_helm: MagicMock) -> None:
        mock_helm.history.side_effect = Exception("not found")
        result = check_helm_release(mock_helm, "ilum")
        assert result.status == CheckStatus.WARN


class TestDoctorRunner:
    def test_skips_cluster_checks_when_unreachable(
        self, mock_helm: MagicMock, mock_k8s: MagicMock
    ) -> None:
        mock_k8s.is_connected.return_value = False
        console = IlumConsole()
        runner = DoctorRunner(helm=mock_helm, k8s=mock_k8s, console=console)
        results = runner.run_all()
        skipped = [r for r in results if r.status == CheckStatus.SKIP]
        # namespace, pods, pvcs, rbac, release, compatibility, storage-class,
        # resources, service-endpoints, health-endpoints
        assert len(skipped) >= 8

    def test_all_pass_when_healthy(self, mock_helm: MagicMock, mock_k8s: MagicMock) -> None:
        mock_helm.history.return_value = HelmResult(
            returncode=0,
            stdout="",
            stderr="",
            json_data=[{"status": "deployed"}],
        )
        mock_helm._run.return_value = HelmResult(
            returncode=0,
            stdout="[]",
            stderr="",
            json_data=[{"name": "ilum", "url": "https://charts.ilum.cloud"}],
        )
        console = IlumConsole()
        runner = DoctorRunner(helm=mock_helm, k8s=mock_k8s, console=console)
        results = runner.run_all()
        check_names = ("cluster", "namespace", "pods", "pvcs", "rbac")
        cluster_checks = [r for r in results if r.name in check_names]
        for r in cluster_checks:
            assert r.status == CheckStatus.OK


class TestServiceEndpoints:
    def test_all_ready(self, mock_k8s: MagicMock) -> None:
        mock_k8s.list_service_endpoints.return_value = True
        result = check_service_endpoints(mock_k8s, "default")
        assert result.status == CheckStatus.OK

    def test_missing_endpoints(self, mock_k8s: MagicMock) -> None:
        mock_k8s.list_service_endpoints.return_value = False
        result = check_service_endpoints(mock_k8s, "default")
        assert result.status == CheckStatus.WARN
        assert "without ready endpoints" in result.message

    def test_skip_on_error(self, mock_k8s: MagicMock) -> None:
        mock_k8s.list_service_endpoints.side_effect = Exception("api error")
        result = check_service_endpoints(mock_k8s, "default")
        assert result.status == CheckStatus.SKIP


class TestHealthEndpoints:
    def test_all_healthy(self, mock_k8s: MagicMock) -> None:
        mock_k8s.list_pods_by_label.return_value = [
            PodStatus(
                name="ilum-core-0",
                namespace="default",
                phase="Running",
                ready=True,
                restart_count=0,
            ),
        ]
        mock_k8s.check_health_endpoint.return_value = (True, '{"status":"UP"}')
        result = check_health_endpoints(mock_k8s, "default")
        assert result.status == CheckStatus.OK

    def test_unhealthy_endpoint(self, mock_k8s: MagicMock) -> None:
        mock_k8s.list_pods_by_label.return_value = [
            PodStatus(
                name="ilum-core-0",
                namespace="default",
                phase="Running",
                ready=True,
                restart_count=0,
            ),
        ]
        mock_k8s.check_health_endpoint.return_value = (False, "HTTP 503: Service Unavailable")
        result = check_health_endpoints(mock_k8s, "default")
        assert result.status == CheckStatus.WARN
        assert "Unhealthy" in result.message

    def test_no_running_pods(self, mock_k8s: MagicMock) -> None:
        mock_k8s.list_pods_by_label.return_value = []
        result = check_health_endpoints(mock_k8s, "default")
        assert result.status == CheckStatus.SKIP

    def test_skip_on_error(self, mock_k8s: MagicMock) -> None:
        mock_k8s.list_pods_by_label.side_effect = Exception("api error")
        result = check_health_endpoints(mock_k8s, "default")
        assert result.status == CheckStatus.SKIP
