"""Tests for Ingress controller, cert-manager, and LB support detection."""

from __future__ import annotations

from unittest.mock import patch

import pytest

from kuberoku.sdk.ingress import (
    _is_docker_desktop,
    _is_k3s,
    _is_kind,
    _is_minikube,
    detect_cert_manager,
    detect_ingress_controller,
    detect_lb_support,
)
from tests.backends.fake import FakeK8sClient


@pytest.fixture()
def k8s() -> FakeK8sClient:
    return FakeK8sClient()


# ── detect_ingress_controller ───────────────────────────────────


class TestDetectIngressController:
    def test_no_ingress_class_returns_none(self, k8s: FakeK8sClient) -> None:
        result = detect_ingress_controller(k8s, "default")
        assert result is None

    def test_nginx_controller_supports_tcp_proxy(self, k8s: FakeK8sClient) -> None:
        """nginx controllers get supports_tcp_proxy=True."""
        k8s.create_ingress_class(
            {
                "apiVersion": "networking.k8s.io/v1",
                "kind": "IngressClass",
                "metadata": {"name": "nginx"},
                "spec": {"controller": "k8s.io/ingress-nginx"},
            }
        )
        result = detect_ingress_controller(k8s, "default")
        assert result is not None
        assert result.name == "nginx"
        assert result.class_name == "nginx"
        assert result.supports_tcp_proxy is True

    def test_non_nginx_controller(self, k8s: FakeK8sClient) -> None:
        """Any non-nginx controller: uses IngressClass name, no tcp proxy."""
        k8s.create_ingress_class(
            {
                "apiVersion": "networking.k8s.io/v1",
                "kind": "IngressClass",
                "metadata": {"name": "mycontroller"},
                "spec": {"controller": "example.com/my-ingress"},
            }
        )
        result = detect_ingress_controller(k8s, "default")
        assert result is not None
        assert result.name == "mycontroller"
        assert result.class_name == "mycontroller"
        assert result.supports_tcp_proxy is False

    def test_rbac_denied_returns_none(self, k8s: FakeK8sClient) -> None:
        """Can't list IngressClasses → returns None."""
        with patch.object(
            k8s,
            "list_ingress_classes",
            side_effect=PermissionError("ingressclasses is forbidden"),
        ):
            result = detect_ingress_controller(k8s, "default")
        assert result is None


# ── detect_cert_manager ─────────────────────────────────────────


class TestDetectCertManager:
    def test_not_installed(self, k8s: FakeK8sClient) -> None:
        assert detect_cert_manager(k8s) is False

    def test_installed(self, k8s: FakeK8sClient) -> None:
        k8s.create_deployment(
            "cert-manager",
            {
                "apiVersion": "apps/v1",
                "kind": "Deployment",
                "metadata": {"name": "cert-manager"},
                "spec": {
                    "replicas": 1,
                    "selector": {"matchLabels": {"app": "cert-manager"}},
                    "template": {
                        "metadata": {"labels": {"app": "cert-manager"}},
                        "spec": {"containers": [{"name": "cm", "image": "cert-manager:v1.14"}]},
                    },
                },
            },
        )
        assert detect_cert_manager(k8s) is True


# ── detect_lb_support ───────────────────────────────────────────


class TestDetectLbSupport:
    def test_k3s_has_servicelib(self, k8s: FakeK8sClient) -> None:
        k8s.inject_nodes(
            [
                {
                    "apiVersion": "v1",
                    "kind": "Node",
                    "metadata": {
                        "name": "k3s-node",
                        "labels": {"node.kubernetes.io/instance-type": "k3s"},
                    },
                }
            ]
        )
        supported, desc = detect_lb_support(k8s)
        assert supported is True
        assert "k3s" in desc

    def test_docker_desktop(self, k8s: FakeK8sClient) -> None:
        k8s.inject_nodes(
            [
                {
                    "apiVersion": "v1",
                    "kind": "Node",
                    "metadata": {"name": "docker-desktop"},
                }
            ]
        )
        supported, desc = detect_lb_support(k8s)
        assert supported is True
        assert "Docker Desktop" in desc

    def test_minikube(self, k8s: FakeK8sClient) -> None:
        k8s.inject_nodes(
            [
                {
                    "apiVersion": "v1",
                    "kind": "Node",
                    "metadata": {
                        "name": "minikube",
                        "labels": {"minikube.k8s.io/name": "minikube"},
                    },
                }
            ]
        )
        supported, desc = detect_lb_support(k8s)
        assert supported is True
        assert "minikube" in desc

    def test_kind_without_metallb(self, k8s: FakeK8sClient) -> None:
        k8s.inject_nodes(
            [
                {
                    "apiVersion": "v1",
                    "kind": "Node",
                    "metadata": {"name": "kind-control-plane"},
                }
            ]
        )
        supported, desc = detect_lb_support(k8s)
        assert supported is False
        assert "kind" in desc

    def test_kind_with_metallb(self, k8s: FakeK8sClient) -> None:
        k8s.inject_nodes(
            [
                {
                    "apiVersion": "v1",
                    "kind": "Node",
                    "metadata": {"name": "kind-control-plane"},
                }
            ]
        )
        k8s.create_deployment(
            "metallb-system",
            {
                "apiVersion": "apps/v1",
                "kind": "Deployment",
                "metadata": {"name": "metallb-controller"},
                "spec": {
                    "replicas": 1,
                    "selector": {"matchLabels": {"app": "metallb"}},
                    "template": {
                        "metadata": {"labels": {"app": "metallb"}},
                        "spec": {"containers": [{"name": "c", "image": "metallb:latest"}]},
                    },
                },
            },
        )
        supported, desc = detect_lb_support(k8s)
        assert supported is True
        assert "MetalLB" in desc

    def test_cloud_default(self, k8s: FakeK8sClient) -> None:
        # Default node (no special labels) → cloud provider
        supported, desc = detect_lb_support(k8s)
        assert supported is True
        assert "cloud" in desc


# ── RBAC-denied scenarios ──────────────────────────────────────
#
# Real-world: user has limited RBAC (can create deployments but
# can't list nodes or deployments in system namespaces).
# Detection must degrade gracefully, not crash.


class TestDetectionWithRBACDenied:
    """Detection functions handle PermissionError (RBAC denied) gracefully."""

    def test_is_k3s_when_nodes_forbidden(self, k8s: FakeK8sClient) -> None:
        """User lacks RBAC to list nodes → _is_k3s returns False, not crash."""
        with patch.object(k8s, "list_nodes", side_effect=PermissionError("nodes is forbidden")):
            assert _is_k3s(k8s) is False

    def test_is_docker_desktop_when_nodes_forbidden(self, k8s: FakeK8sClient) -> None:
        with patch.object(k8s, "list_nodes", side_effect=PermissionError("nodes is forbidden")):
            assert _is_docker_desktop(k8s) is False

    def test_is_kind_when_nodes_forbidden(self, k8s: FakeK8sClient) -> None:
        with patch.object(k8s, "list_nodes", side_effect=PermissionError("nodes is forbidden")):
            assert _is_kind(k8s) is False

    def test_is_minikube_when_nodes_forbidden(self, k8s: FakeK8sClient) -> None:
        with patch.object(k8s, "list_nodes", side_effect=PermissionError("nodes is forbidden")):
            assert _is_minikube(k8s) is False

    def test_detect_cert_manager_when_forbidden(self, k8s: FakeK8sClient) -> None:
        """Can't list deployments in cert-manager ns → returns False."""
        original_list_deployments = k8s.list_deployments

        def rbac_denied(ns: str, labels: str | None = None) -> list:  # type: ignore[type-arg]
            if ns == "cert-manager":
                raise PermissionError("deployments in cert-manager is forbidden")
            return original_list_deployments(ns, labels)

        with patch.object(k8s, "list_deployments", side_effect=rbac_denied):
            assert detect_cert_manager(k8s) is False

    def test_detect_lb_support_metallb_check_forbidden(self, k8s: FakeK8sClient) -> None:
        """Can't list deployments in metallb-system → falls through to cloud default."""
        original_list_deployments = k8s.list_deployments

        def rbac_denied(ns: str, labels: str | None = None) -> list:  # type: ignore[type-arg]
            if ns == "metallb-system":
                raise PermissionError("deployments in metallb-system is forbidden")
            return original_list_deployments(ns, labels)

        with patch.object(k8s, "list_deployments", side_effect=rbac_denied):
            supported, desc = detect_lb_support(k8s)
        assert supported is True
        assert "cloud" in desc
