"""Tests for CLI gap fixes (G1-G10) from the walkthrough plan."""

from __future__ import annotations

import json
from unittest.mock import MagicMock, patch

import pytest
from typer.testing import CliRunner

from ilum.cli.main import app
from ilum.config.models import ClusterConfig, IlumConfig, ProfileConfig
from ilum.core.kubernetes import PodStatus
from ilum.core.release import ReleaseInfo


@pytest.fixture()
def runner() -> CliRunner:
    return CliRunner()


# ── G2: deps list JSON output ───────────────────────────────────────


class TestDepsListJson:
    def test_json_output(self, runner: CliRunner) -> None:
        result = runner.invoke(app, ["--output", "json", "deps", "list"])
        assert result.exit_code == 0
        data = json.loads(result.output)
        assert isinstance(data, list)
        assert len(data) > 0
        assert "name" in data[0]
        assert "status" in data[0]
        assert "version" in data[0]
        assert "path" in data[0]

    def test_yaml_output(self, runner: CliRunner) -> None:
        result = runner.invoke(app, ["--output", "yaml", "deps", "list"])
        assert result.exit_code == 0
        assert "name:" in result.output


# ── G3: cluster list JSON output ────────────────────────────────────


class TestClusterListJson:
    def test_json_output_empty(self, runner: CliRunner) -> None:
        """Empty cluster list still succeeds (shows info message)."""
        with (
            patch("ilum.cli.cluster_cmd.IlumPaths") as mock_paths_cls,
            patch("ilum.cli.cluster_cmd.ConfigManager") as mock_cm,
            patch("ilum.cli.cluster_cmd.ClusterManager") as mock_cluster_mgr,
        ):
            mp = MagicMock()
            mock_paths_cls.default.return_value = mp
            mock_cfg = MagicMock()
            mock_cfg.clusters = []
            mock_cm.return_value.ensure_config.return_value = mock_cfg
            mock_cluster_mgr.list_local.return_value = []
            result = runner.invoke(app, ["--output", "json", "cluster", "list"])
        assert result.exit_code == 0

    def test_json_output_with_clusters(self, runner: CliRunner) -> None:
        """JSON output contains expected fields."""
        from ilum.config.models import ClusterRecord

        record = ClusterRecord(
            name="test-cluster",
            provider="k3d",
            kubecontext="k3d-test-cluster",
            status="Running",
            source="managed",
            created_at="2026-01-15T10:00:00",
        )
        with (
            patch("ilum.cli.cluster_cmd.IlumPaths") as mock_paths_cls,
            patch("ilum.cli.cluster_cmd.ConfigManager") as mock_cm,
            patch("ilum.cli.cluster_cmd.ClusterManager") as mock_cluster_mgr,
        ):
            mp = MagicMock()
            mock_paths_cls.default.return_value = mp
            mock_cfg = MagicMock()
            mock_cfg.clusters = [record]
            mock_cm.return_value.ensure_config.return_value = mock_cfg
            mock_cluster_mgr.list_local.return_value = [record]
            result = runner.invoke(app, ["--output", "json", "cluster", "list"])
        assert result.exit_code == 0
        data = json.loads(result.output)
        assert isinstance(data, list)
        assert len(data) == 1
        assert data[0]["name"] == "test-cluster"
        assert data[0]["provider"] == "k3d"


# ── G4: preset list JSON output ─────────────────────────────────────


class TestPresetListJson:
    def test_json_output(self, runner: CliRunner) -> None:
        result = runner.invoke(app, ["--output", "json", "preset", "list"])
        assert result.exit_code == 0
        data = json.loads(result.output)
        assert isinstance(data, list)
        assert len(data) == 4
        names = [p["name"] for p in data]
        assert "development" in names
        assert "production" in names
        for p in data:
            assert "modules" in p
            assert "module_count" in p
            assert isinstance(p["modules"], list)

    def test_yaml_output(self, runner: CliRunner) -> None:
        result = runner.invoke(app, ["--output", "yaml", "preset", "list"])
        assert result.exit_code == 0
        assert "name: development" in result.output


# ── G5: config list-profiles JSON output ─────────────────────────────


class TestConfigListProfilesJson:
    def test_json_output(self, runner: CliRunner) -> None:
        with (
            patch("ilum.cli.config_cmd.ConfigManager") as mock_cm_cls,
        ):
            mock_mgr = MagicMock()
            mock_mgr.load.return_value = IlumConfig(
                active_profile="default",
                profiles={
                    "default": ProfileConfig(
                        name="default",
                        release_name="ilum",
                        cluster=ClusterConfig(namespace="default"),
                        enabled_modules=["core", "ui"],
                    ),
                },
            )
            mock_cm_cls.return_value = mock_mgr
            result = runner.invoke(app, ["--output", "json", "config", "list-profiles"])

        assert result.exit_code == 0
        data = json.loads(result.output)
        assert isinstance(data, list)
        assert len(data) == 1
        assert data[0]["name"] == "default"
        assert data[0]["active"] is True
        assert data[0]["module_count"] == 2
        assert data[0]["modules"] == ["core", "ui"]


# ── G6: logs --max-duration ──────────────────────────────────────────


class TestLogsMaxDuration:
    def test_max_duration_flag_in_help(self, runner: CliRunner) -> None:
        result = runner.invoke(app, ["logs", "--help"])
        assert result.exit_code == 0
        assert "--max-duration" in result.output

    def test_max_duration_stops_follow(self, runner: CliRunner) -> None:
        """--max-duration with 0 delay stream stops after time check."""
        mock_k8s = MagicMock()
        mock_k8s.list_pods_by_label.return_value = [
            PodStatus(
                name="ilum-core-abc",
                namespace="default",
                phase="Running",
                ready=True,
                restart_count=0,
                containers=("core",),
            )
        ]
        # Simulate an infinite stream; max_duration should break it
        mock_k8s.stream_pod_log.return_value = iter(["line1", "line2", "line3"])

        with (
            patch("ilum.cli.logs_cmd.KubeClient", return_value=mock_k8s),
            patch("ilum.cli.logs_cmd.time") as mock_time,
        ):
            # First call returns 0, subsequent calls return > max_duration
            mock_time.monotonic.side_effect = [0.0, 0.5, 5.0, 10.0]
            result = runner.invoke(app, ["logs", "core", "--follow", "--max-duration", "1"])
        assert result.exit_code == 0
        assert "Reached max duration" in result.output


# ── G7: connect --no-switch ──────────────────────────────────────────


class TestConnectNoSwitch:
    def test_no_switch_flag_in_help(self, runner: CliRunner) -> None:
        result = runner.invoke(app, ["connect", "--help"])
        assert result.exit_code == 0
        assert "--no-switch" in result.output

    def test_no_switch_preserves_active_profile(self, runner: CliRunner) -> None:
        from ilum.core.modules import ModuleResolver
        from ilum.core.release import ReleaseManager

        mock_mgr = MagicMock(spec=ReleaseManager)
        mock_mgr.resolver = ModuleResolver()
        mock_mgr.config_mgr = MagicMock()
        mock_mgr.config_mgr.load.return_value = IlumConfig(
            active_profile="original",
            profiles={
                "original": ProfileConfig(name="original"),
                "staging": ProfileConfig(name="staging"),
            },
        )
        mock_mgr.paths = MagicMock()
        mock_mgr.paths.state_dir = "/tmp/test-state"
        mock_mgr.fetch_live_values.return_value = {}

        info = ReleaseInfo(
            name="ilum",
            namespace="default",
            status="deployed",
            chart="ilum",
            chart_version="6.7.0",
            revision=1,
            last_deployed="2026-01-15T10:00:00Z",
        )
        mock_mgr.scan_releases.return_value = [info]
        mock_mgr.get_release_info.return_value = info
        mock_mgr.fetch_computed_values.return_value = {}
        mock_mgr.resolver.detect_enabled_modules = MagicMock(return_value=[])

        with (
            patch("ilum.cli.connect_cmd._build_manager", return_value=mock_mgr),
            patch("ilum.cli.connect_cmd.save_snapshot"),
        ):
            result = runner.invoke(app, ["connect", "--yes", "--no-switch", "--profile", "staging"])

        assert result.exit_code == 0
        saved_config = mock_mgr.config_mgr.save.call_args[0][0]
        # active_profile should NOT have switched to "staging"
        assert saved_config.active_profile == "original"


# ── G8: openldap in presets ──────────────────────────────────────────


class TestPresetsOpenldap:
    def test_development_includes_openldap(self) -> None:
        from ilum.core.presets import PRESETS

        assert "openldap" in PRESETS["development"].modules

    def test_data_engineering_includes_openldap(self) -> None:
        from ilum.core.presets import PRESETS

        assert "openldap" in PRESETS["data-engineering"].modules


# ── G10: CRD pre-install ────────────────────────────────────────────


class TestMonitoringCrdField:
    def test_monitoring_has_crd_chart(self) -> None:
        from ilum.core.modules import ModuleResolver

        resolver = ModuleResolver()
        mod = resolver.get("monitoring")
        assert mod.crd_chart == "prometheus-community/prometheus-operator-crds"

    def test_other_modules_no_crd_chart(self) -> None:
        from ilum.core.modules import ModuleResolver

        resolver = ModuleResolver()
        for mod in resolver.all_modules():
            if mod.name != "monitoring":
                assert mod.crd_chart == "", f"{mod.name} should not have crd_chart"


class TestEnsureModuleCrds:
    def test_ensure_crds_installs_when_missing(self) -> None:
        from ilum.core.release import ReleaseManager

        mgr = MagicMock(spec=ReleaseManager)
        mgr.resolver = MagicMock()

        mod = MagicMock()
        mod.crd_chart = "prometheus-community/prometheus-operator-crds"
        mgr.resolver.get.return_value = mod

        mgr.helm = MagicMock()
        mgr.helm.release_exists.return_value = False

        # Call the real method
        ReleaseManager.ensure_module_crds(mgr, ["monitoring"])

        mgr.helm.repo_add.assert_called_once_with(
            "prometheus-community",
            "https://prometheus-community.github.io/helm-charts",
        )
        mgr.helm.install.assert_called_once_with(
            release="monitoring-crds",
            chart="prometheus-community/prometheus-operator-crds",
        )

    def test_ensure_crds_skips_when_exists(self) -> None:
        from ilum.core.release import ReleaseManager

        mgr = MagicMock(spec=ReleaseManager)
        mgr.resolver = MagicMock()

        mod = MagicMock()
        mod.crd_chart = "prometheus-community/prometheus-operator-crds"
        mgr.resolver.get.return_value = mod

        mgr.helm = MagicMock()
        mgr.helm.release_exists.return_value = True

        ReleaseManager.ensure_module_crds(mgr, ["monitoring"])

        mgr.helm.install.assert_not_called()

    def test_ensure_crds_skips_no_crd_chart(self) -> None:
        from ilum.core.release import ReleaseManager

        mgr = MagicMock(spec=ReleaseManager)
        mgr.resolver = MagicMock()

        mod = MagicMock()
        mod.crd_chart = ""
        mgr.resolver.get.return_value = mod

        mgr.helm = MagicMock()

        ReleaseManager.ensure_module_crds(mgr, ["core"])

        mgr.helm.release_exists.assert_not_called()
        mgr.helm.install.assert_not_called()
