"""Tests for ClustersService — add, remove, list, switch, current, info."""

from __future__ import annotations

import pathlib
from unittest.mock import patch

import pytest

from kuberoku.exceptions import (
    ClusterAlreadyExistsError,
    ClusterNotFoundError,
    NoCurrentClusterError,
)
from kuberoku.factory import KuberokuFactory
from kuberoku.models import ClusterConfig, ClusterInfo
from kuberoku.sdk.clusters import detect_kubeconfig_context
from tests.backends.fake import FakeK8sClient


def _make_factory(tmp_path: pathlib.Path) -> KuberokuFactory:
    """Create a factory with a ClustersService using tmp_path for config."""
    k8s = FakeK8sClient()
    factory = KuberokuFactory(k8s_client=k8s, namespace="test-ns")
    factory.clusters.set_config_path(tmp_path / "config.yaml")
    return factory


class TestClustersAdd:
    """Tests for ClustersService.add()."""

    def test_add_cluster(self, tmp_path: pathlib.Path) -> None:
        factory = _make_factory(tmp_path)
        result = factory.clusters.add("staging", "staging-gke", namespace="staging-apps")
        assert isinstance(result, ClusterConfig)
        assert result.name == "staging"
        assert result.context == "staging-gke"
        assert result.namespace == "staging-apps"

    def test_add_sets_current_if_first(self, tmp_path: pathlib.Path) -> None:
        factory = _make_factory(tmp_path)
        factory.clusters.add("staging", "staging-gke")
        assert factory.clusters.current() == "staging"

    def test_add_duplicate_raises(self, tmp_path: pathlib.Path) -> None:
        factory = _make_factory(tmp_path)
        factory.clusters.add("staging", "staging-gke")
        with pytest.raises(ClusterAlreadyExistsError):
            factory.clusters.add("staging", "different-context")

    def test_add_with_default(self, tmp_path: pathlib.Path) -> None:
        factory = _make_factory(tmp_path)
        factory.clusters.add("staging", "staging-gke")
        factory.clusters.add("prod", "prod-eks", default=True)
        assert factory.clusters.current() == "prod"

    def test_add_on_step_callback(self, tmp_path: pathlib.Path) -> None:
        factory = _make_factory(tmp_path)
        steps: list[str] = []
        factory.clusters.add("staging", "staging-gke", on_step=steps.append)
        assert len(steps) == 2
        assert "Adding" in steps[0]
        assert "registered" in steps[1]


class TestClustersRemove:
    """Tests for ClustersService.remove()."""

    def test_remove_existing(self, tmp_path: pathlib.Path) -> None:
        factory = _make_factory(tmp_path)
        factory.clusters.add("staging", "staging-gke")
        factory.clusters.remove("staging")
        assert factory.clusters.list() == ()

    def test_remove_nonexistent_raises(self, tmp_path: pathlib.Path) -> None:
        factory = _make_factory(tmp_path)
        with pytest.raises(ClusterNotFoundError):
            factory.clusters.remove("nope")

    def test_remove_current_switches(self, tmp_path: pathlib.Path) -> None:
        factory = _make_factory(tmp_path)
        factory.clusters.add("staging", "staging-gke")
        factory.clusters.add("prod", "prod-eks")
        factory.clusters.remove("staging")
        assert factory.clusters.current() == "prod"


class TestClustersList:
    """Tests for ClustersService.list()."""

    def test_list_empty(self, tmp_path: pathlib.Path) -> None:
        factory = _make_factory(tmp_path)
        assert factory.clusters.list() == ()

    def test_list_multiple(self, tmp_path: pathlib.Path) -> None:
        factory = _make_factory(tmp_path)
        factory.clusters.add("staging", "staging-gke")
        factory.clusters.add("prod", "prod-eks")
        clusters = factory.clusters.list()
        assert len(clusters) == 2
        names = {c.name for c in clusters}
        assert names == {"staging", "prod"}


class TestClustersSwitch:
    """Tests for ClustersService.switch()."""

    def test_switch(self, tmp_path: pathlib.Path) -> None:
        factory = _make_factory(tmp_path)
        factory.clusters.add("staging", "staging-gke")
        factory.clusters.add("prod", "prod-eks")
        factory.clusters.switch("prod")
        assert factory.clusters.current() == "prod"

    def test_switch_nonexistent_raises(self, tmp_path: pathlib.Path) -> None:
        factory = _make_factory(tmp_path)
        with pytest.raises(ClusterNotFoundError):
            factory.clusters.switch("nope")


class TestClustersCurrent:
    """Tests for ClustersService.current()."""

    def test_no_current_raises(self, tmp_path: pathlib.Path) -> None:
        factory = _make_factory(tmp_path)
        with (
            patch(
                "kuberoku.sdk.clusters.detect_kubeconfig_context",
                return_value=(None, None),
            ),
            pytest.raises(NoCurrentClusterError),
        ):
            factory.clusters.current()

    def test_current_fallback_to_kubeconfig(self, tmp_path: pathlib.Path) -> None:
        factory = _make_factory(tmp_path)
        with patch(
            "kuberoku.sdk.clusters.detect_kubeconfig_context",
            return_value=("docker-desktop", "docker-desktop"),
        ):
            assert factory.clusters.current() == "docker-desktop"

    def test_current_after_add(self, tmp_path: pathlib.Path) -> None:
        factory = _make_factory(tmp_path)
        factory.clusters.add("staging", "staging-gke")
        assert factory.clusters.current() == "staging"


class TestClustersInfo:
    """Tests for ClustersService.info()."""

    def test_info_basic(self, tmp_path: pathlib.Path) -> None:
        factory = _make_factory(tmp_path)
        factory.clusters.add("staging", "staging-gke", namespace="staging-apps")
        result = factory.clusters.info("staging")
        assert isinstance(result, ClusterInfo)
        assert result.name == "staging"
        assert result.context == "staging-gke"
        assert result.is_current is True
        assert result.k8s_version == "1.31"
        assert result.node_count == 1

    def test_info_nonexistent_raises(self, tmp_path: pathlib.Path) -> None:
        factory = _make_factory(tmp_path)
        with pytest.raises(ClusterNotFoundError):
            factory.clusters.info("nope")

    def test_info_is_current_false(self, tmp_path: pathlib.Path) -> None:
        factory = _make_factory(tmp_path)
        factory.clusters.add("staging", "staging-gke")
        factory.clusters.add("prod", "prod-eks")
        factory.clusters.switch("prod")
        result = factory.clusters.info("staging")
        assert result.is_current is False

    def test_info_live_data_error_returns_none(self, tmp_path: pathlib.Path) -> None:
        """When K8s connection fails, info returns None for live fields."""
        factory = _make_factory(tmp_path)
        factory.clusters.add("staging", "staging-gke")
        with patch.object(
            factory.k8s,
            "server_version",
            side_effect=ConnectionError("offline"),
        ):
            result = factory.clusters.info("staging")
        assert result.name == "staging"
        assert result.k8s_version is None
        assert result.node_count is None
        assert result.app_count is None


class TestDetectKubeconfigContext:
    """Tests for detect_kubeconfig_context()."""

    def test_returns_context_and_cluster(self) -> None:
        active = {"name": "my-ctx", "context": {"cluster": "my-cluster"}}
        with patch(
            "kuberoku.sdk.clusters.kubernetes.config.list_kube_config_contexts",
            return_value=([], active),
        ):
            ctx, cluster = detect_kubeconfig_context()
        assert ctx == "my-ctx"
        assert cluster == "my-cluster"

    def test_returns_none_on_error(self) -> None:
        with patch(
            "kuberoku.sdk.clusters.kubernetes.config.list_kube_config_contexts",
            side_effect=Exception("no kubeconfig"),
        ):
            ctx, cluster = detect_kubeconfig_context()
        assert ctx is None
        assert cluster is None

    def test_returns_none_when_active_is_none(self) -> None:
        with patch(
            "kuberoku.sdk.clusters.kubernetes.config.list_kube_config_contexts",
            return_value=([], None),
        ):
            ctx, cluster = detect_kubeconfig_context()
        assert ctx is None
        assert cluster is None

    def test_cluster_defaults_to_context_name(self) -> None:
        active = {"name": "docker-desktop", "context": {}}
        with patch(
            "kuberoku.sdk.clusters.kubernetes.config.list_kube_config_contexts",
            return_value=([], active),
        ):
            ctx, cluster = detect_kubeconfig_context()
        assert ctx == "docker-desktop"
        assert cluster == "docker-desktop"
