"""CLI tests for clusters:* commands."""

from __future__ import annotations

import pathlib
from typing import Any
from unittest.mock import patch

import pytest
from click.testing import CliRunner

from kuberoku.cli.main import cli
from kuberoku.factory import KuberokuFactory
from tests.backends.fake import FakeK8sClient


def _make_factory_with_config(tmp_path: pathlib.Path) -> KuberokuFactory:
    k8s = FakeK8sClient()
    factory = KuberokuFactory(k8s_client=k8s, namespace="test-ns")
    factory.clusters.set_config_path(tmp_path / "config.yaml")
    return factory


class TestClustersListCLI:
    """Tests for 'clusters' (list) command."""

    def test_empty_with_kubeconfig(self, cli_runner: CliRunner, tmp_path: pathlib.Path) -> None:
        factory = _make_factory_with_config(tmp_path)
        with patch(
            "kuberoku.cli.clusters.detect_kubeconfig_context",
            return_value=("minikube", "minikube"),
        ):
            result = cli_runner.invoke(cli, ["clusters"], obj={"factory": factory})
        assert result.exit_code == 0
        assert "minikube" in result.output
        assert "auto-detected" in result.output

    def test_empty_no_kubeconfig(self, cli_runner: CliRunner, tmp_path: pathlib.Path) -> None:
        factory = _make_factory_with_config(tmp_path)
        with patch(
            "kuberoku.cli.clusters.detect_kubeconfig_context",
            return_value=(None, None),
        ):
            result = cli_runner.invoke(cli, ["clusters"], obj={"factory": factory})
        assert result.exit_code == 0
        assert "No Kubernetes cluster found" in result.output

    def test_with_clusters(self, cli_runner: CliRunner, tmp_path: pathlib.Path) -> None:
        factory = _make_factory_with_config(tmp_path)
        factory.clusters.add("staging", "staging-gke", namespace="staging-apps")
        result = cli_runner.invoke(cli, ["clusters"], obj={"factory": factory})
        assert result.exit_code == 0
        assert "staging" in result.output
        assert "staging-gke" in result.output

    def test_current_marked(self, cli_runner: CliRunner, tmp_path: pathlib.Path) -> None:
        factory = _make_factory_with_config(tmp_path)
        factory.clusters.add("staging", "staging-gke")
        result = cli_runner.invoke(cli, ["clusters"], obj={"factory": factory})
        assert "*" in result.output


class TestClustersAddCLI:
    """Tests for 'clusters:add' command."""

    def test_add(self, cli_runner: CliRunner, tmp_path: pathlib.Path) -> None:
        factory = _make_factory_with_config(tmp_path)
        result = cli_runner.invoke(
            cli,
            ["clusters:add", "staging", "--context", "staging-gke"],
            obj={"factory": factory},
        )
        assert result.exit_code == 0
        assert "Added cluster staging" in result.output

    def test_add_with_namespace(self, cli_runner: CliRunner, tmp_path: pathlib.Path) -> None:
        factory = _make_factory_with_config(tmp_path)
        result = cli_runner.invoke(
            cli,
            ["clusters:add", "staging", "--context", "staging-gke", "--namespace", "staging-apps"],
            obj={"factory": factory},
        )
        assert result.exit_code == 0

    def test_add_with_default(self, cli_runner: CliRunner, tmp_path: pathlib.Path) -> None:
        factory = _make_factory_with_config(tmp_path)
        factory.clusters.add("staging", "staging-gke")
        result = cli_runner.invoke(
            cli,
            ["clusters:add", "prod", "--context", "prod-eks", "--default"],
            obj={"factory": factory},
        )
        assert result.exit_code == 0
        assert factory.clusters.current() == "prod"

    def test_add_duplicate_fails(self, cli_runner: CliRunner, tmp_path: pathlib.Path) -> None:
        factory = _make_factory_with_config(tmp_path)
        factory.clusters.add("staging", "staging-gke")
        result = cli_runner.invoke(
            cli,
            ["clusters:add", "staging", "--context", "other"],
            obj={"factory": factory},
        )
        assert result.exit_code != 0

    def test_add_requires_context(self, cli_runner: CliRunner, tmp_path: pathlib.Path) -> None:
        factory = _make_factory_with_config(tmp_path)
        result = cli_runner.invoke(
            cli,
            ["clusters:add", "staging"],
            obj={"factory": factory},
        )
        assert result.exit_code != 0


class TestClustersRemoveCLI:
    """Tests for 'clusters:remove' command."""

    def test_remove_with_confirm(self, cli_runner: CliRunner, tmp_path: pathlib.Path) -> None:
        factory = _make_factory_with_config(tmp_path)
        factory.clusters.add("staging", "staging-gke")
        result = cli_runner.invoke(
            cli,
            ["clusters:remove", "staging", "--confirm", "staging"],
            obj={"factory": factory},
        )
        assert result.exit_code == 0
        assert "Removed" in result.output

    def test_remove_nonexistent(self, cli_runner: CliRunner, tmp_path: pathlib.Path) -> None:
        factory = _make_factory_with_config(tmp_path)
        result = cli_runner.invoke(
            cli,
            ["clusters:remove", "nope", "--confirm", "nope"],
            obj={"factory": factory},
        )
        assert result.exit_code != 0


class TestClustersSwitchCLI:
    """Tests for 'clusters:switch' command."""

    def test_switch(self, cli_runner: CliRunner, tmp_path: pathlib.Path) -> None:
        factory = _make_factory_with_config(tmp_path)
        factory.clusters.add("staging", "staging-gke")
        factory.clusters.add("prod", "prod-eks")
        result = cli_runner.invoke(
            cli,
            ["clusters:switch", "prod"],
            obj={"factory": factory},
        )
        assert result.exit_code == 0
        assert "Switched" in result.output

    def test_switch_nonexistent(self, cli_runner: CliRunner, tmp_path: pathlib.Path) -> None:
        factory = _make_factory_with_config(tmp_path)
        result = cli_runner.invoke(
            cli,
            ["clusters:switch", "nope"],
            obj={"factory": factory},
        )
        assert result.exit_code != 0


class TestClustersCurrentCLI:
    """Tests for 'clusters:current' command."""

    def test_current(self, cli_runner: CliRunner, tmp_path: pathlib.Path) -> None:
        factory = _make_factory_with_config(tmp_path)
        factory.clusters.add("staging", "staging-gke")
        result = cli_runner.invoke(
            cli,
            ["clusters:current"],
            obj={"factory": factory},
        )
        assert result.exit_code == 0
        assert "staging" in result.output

    def test_current_fallback_to_kubeconfig(
        self, cli_runner: CliRunner, tmp_path: pathlib.Path
    ) -> None:
        factory = _make_factory_with_config(tmp_path)
        with patch(
            "kuberoku.sdk.clusters.detect_kubeconfig_context",
            return_value=("kind-test", "kind-test"),
        ):
            result = cli_runner.invoke(
                cli,
                ["clusters:current"],
                obj={"factory": factory},
            )
        assert result.exit_code == 0
        assert "kind-test" in result.output

    def test_no_current_no_kubeconfig(self, cli_runner: CliRunner, tmp_path: pathlib.Path) -> None:
        factory = _make_factory_with_config(tmp_path)
        with patch(
            "kuberoku.sdk.clusters.detect_kubeconfig_context",
            return_value=(None, None),
        ):
            result = cli_runner.invoke(
                cli,
                ["clusters:current"],
                obj={"factory": factory},
            )
        assert result.exit_code != 0


class TestClustersInfoCLI:
    """Tests for 'clusters:info' command."""

    def test_info(self, cli_runner: CliRunner, tmp_path: pathlib.Path) -> None:
        factory = _make_factory_with_config(tmp_path)
        factory.clusters.add("staging", "staging-gke", namespace="staging-apps")
        result = cli_runner.invoke(
            cli,
            ["clusters:info", "staging"],
            obj={"factory": factory},
        )
        assert result.exit_code == 0
        assert "staging" in result.output
        assert "staging-gke" in result.output


class TestClustersDoctorCLI:
    """Tests for 'clusters:doctor' command."""

    def test_doctor_shows_checks(self, cli_runner: CliRunner, factory: KuberokuFactory) -> None:
        result = cli_runner.invoke(cli, ["clusters:doctor"], obj={"factory": factory})
        assert "[+]" in result.output
        assert "checks passed" in result.output

    def test_doctor_all_pass_with_cni(self, cli_runner: CliRunner) -> None:
        """All 13 checks pass when CNI, ingress, cert-manager, and LB are present."""
        k8s = FakeK8sClient()
        k8s.create_namespace("kube-system")
        k8s.create_daemonset(
            "kube-system",
            {
                "apiVersion": "apps/v1",
                "kind": "DaemonSet",
                "metadata": {"name": "calico-node"},
                "spec": {},
            },
        )
        k8s.create_ingress_class(
            {
                "apiVersion": "networking.k8s.io/v1",
                "kind": "IngressClass",
                "metadata": {"name": "nginx"},
                "spec": {"controller": "k8s.io/ingress-nginx"},
            }
        )
        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"}]},
                    },
                },
            },
        )
        factory = KuberokuFactory(k8s_client=k8s, namespace="test-ns")
        result = cli_runner.invoke(cli, ["clusters:doctor"], obj={"factory": factory})
        assert result.exit_code == 0
        assert "checks passed" in result.output

    def test_doctor_failure_suggests_setup(
        self, cli_runner: CliRunner, factory: KuberokuFactory, request: pytest.FixtureRequest
    ) -> None:
        """When checks fail, doctor suggests running clusters:setup."""
        if request.config.getoption("--backend") == "real":
            pytest.skip("Real K8s cluster is fully configured — no failures to test")
        result = cli_runner.invoke(cli, ["clusters:doctor"], obj={"factory": factory})
        # CNI check fails on FakeK8sClient → should suggest setup
        assert "clusters:setup" in result.output

    def test_doctor_colon_syntax(self, cli_runner: CliRunner, factory: KuberokuFactory) -> None:
        result = cli_runner.invoke(cli, ["clusters:doctor"], obj={"factory": factory})
        assert "checks passed" in result.output

    def test_doctor_space_syntax(self, cli_runner: CliRunner, factory: KuberokuFactory) -> None:
        result = cli_runner.invoke(cli, ["clusters", "doctor"], obj={"factory": factory})
        assert "checks passed" in result.output


class TestClustersSetupCLI:
    """Tests for 'clusters:setup' command."""

    @patch("kuberoku.sdk.checks.subprocess.run")
    def test_setup_runs(
        self,
        mock_run: Any,
        cli_runner: CliRunner,
        factory: KuberokuFactory,
        request: pytest.FixtureRequest,
    ) -> None:
        if request.config.getoption("--backend") == "real":
            pytest.skip("Real K8s cluster is fully configured — no manual steps to test")
        result = cli_runner.invoke(cli, ["clusters:setup"], obj={"factory": factory})
        # CNI check fails on FakeK8sClient — shows manual steps
        assert "[+]" in result.output
        assert "Manual steps needed" in result.output

    def test_setup_all_pass_with_cni(self, cli_runner: CliRunner) -> None:
        k8s = FakeK8sClient()
        k8s.create_namespace("kube-system")
        k8s.create_daemonset(
            "kube-system",
            {
                "apiVersion": "apps/v1",
                "kind": "DaemonSet",
                "metadata": {"name": "calico-node"},
                "spec": {},
            },
        )
        k8s.create_ingress_class(
            {
                "apiVersion": "networking.k8s.io/v1",
                "kind": "IngressClass",
                "metadata": {"name": "nginx"},
                "spec": {"controller": "k8s.io/ingress-nginx"},
            }
        )
        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"}]},
                    },
                },
            },
        )
        factory = KuberokuFactory(k8s_client=k8s, namespace="test-ns")
        result = cli_runner.invoke(cli, ["clusters:setup"], obj={"factory": factory})
        assert result.exit_code == 0
        assert "Cluster is ready" in result.output

    @patch("kuberoku.sdk.checks.subprocess.run")
    def test_setup_creates_namespace(self, mock_run: Any, cli_runner: CliRunner) -> None:
        k8s = FakeK8sClient()
        factory = KuberokuFactory(k8s_client=k8s, namespace="new-ns")
        cli_runner.invoke(cli, ["clusters:setup"], obj={"factory": factory})
        # Namespace created during check phase
        assert k8s.namespace_exists("new-ns")
