"""Tests for ``ilum.core.kubernetes.KubeClient`` — node/service query methods."""

from __future__ import annotations

from unittest.mock import MagicMock

import pytest
from kubernetes.client.exceptions import ApiException

from ilum.core.kubernetes import KubeClient
from ilum.errors import ClusterConnectionError


@pytest.fixture()
def k8s() -> KubeClient:
    """Return a KubeClient with a mocked API client so no real cluster is hit."""
    client = KubeClient.__new__(KubeClient)
    client._kubecontext = ""
    client._client = MagicMock()
    client._core = MagicMock()
    client._auth = None
    client._version = None
    client._batch = None
    return client


# ---------------------------------------------------------------------------
# get_node_address
# ---------------------------------------------------------------------------


class TestGetNodeAddress:
    def test_returns_external_ip(self, k8s: KubeClient) -> None:
        """ExternalIP is preferred over InternalIP."""
        addr_ext = MagicMock()
        addr_ext.type = "ExternalIP"
        addr_ext.address = "1.2.3.4"
        addr_int = MagicMock()
        addr_int.type = "InternalIP"
        addr_int.address = "10.0.0.1"

        node = MagicMock()
        node.status.addresses = [addr_int, addr_ext]

        node_list = MagicMock()
        node_list.items = [node]
        k8s._core.list_node.return_value = node_list

        assert k8s.get_node_address() == "1.2.3.4"

    def test_falls_back_to_internal_ip(self, k8s: KubeClient) -> None:
        """InternalIP is returned when no ExternalIP exists."""
        addr_int = MagicMock()
        addr_int.type = "InternalIP"
        addr_int.address = "10.0.0.5"

        node = MagicMock()
        node.status.addresses = [addr_int]

        node_list = MagicMock()
        node_list.items = [node]
        k8s._core.list_node.return_value = node_list

        assert k8s.get_node_address() == "10.0.0.5"

    def test_raises_on_no_nodes(self, k8s: KubeClient) -> None:
        """ClusterConnectionError when the cluster has no nodes."""
        node_list = MagicMock()
        node_list.items = []
        k8s._core.list_node.return_value = node_list

        with pytest.raises(ClusterConnectionError, match="No nodes"):
            k8s.get_node_address()

    def test_raises_on_api_error(self, k8s: KubeClient) -> None:
        k8s._core.list_node.side_effect = ApiException(status=403, reason="Forbidden")

        with pytest.raises(ClusterConnectionError, match="Failed to list nodes"):
            k8s.get_node_address()


# ---------------------------------------------------------------------------
# get_service_node_port
# ---------------------------------------------------------------------------


class TestGetServiceNodePort:
    def test_returns_node_port(self, k8s: KubeClient) -> None:
        port = MagicMock()
        port.node_port = 31777

        svc = MagicMock()
        svc.spec.ports = [port]
        k8s._core.read_namespaced_service.return_value = svc

        assert k8s.get_service_node_port("ilum-ui", "default") == 31777

    def test_raises_on_not_found(self, k8s: KubeClient) -> None:
        k8s._core.read_namespaced_service.side_effect = ApiException(status=404, reason="Not Found")

        with pytest.raises(ClusterConnectionError, match="not found"):
            k8s.get_service_node_port("ilum-ui", "default")

    def test_raises_on_no_nodeport(self, k8s: KubeClient) -> None:
        """Service exists but has no NodePort allocated."""
        port = MagicMock()
        port.node_port = None

        svc = MagicMock()
        svc.spec.ports = [port]
        k8s._core.read_namespaced_service.return_value = svc

        with pytest.raises(ClusterConnectionError, match="no NodePort"):
            k8s.get_service_node_port("ilum-ui", "default")

    def test_raises_on_empty_ports(self, k8s: KubeClient) -> None:
        svc = MagicMock()
        svc.spec.ports = []
        k8s._core.read_namespaced_service.return_value = svc

        with pytest.raises(ClusterConnectionError, match="no NodePort"):
            k8s.get_service_node_port("ilum-ui", "default")
