"""Tests for ``ilum connect`` command."""

from __future__ import annotations

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.release import ReleaseInfo
from ilum.errors import ClusterConnectionError, ReleaseNotFoundError


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


@pytest.fixture()
def mock_mgr() -> MagicMock:
    from ilum.core.modules import ModuleResolver
    from ilum.core.release import ReleaseManager

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


def _make_release_info(
    name: str = "ilum",
    namespace: str = "default",
    chart_version: str = "6.7.0",
    status: str = "deployed",
) -> ReleaseInfo:
    return ReleaseInfo(
        name=name,
        namespace=namespace,
        status=status,
        chart="ilum",
        chart_version=chart_version,
        revision=1,
        last_deployed="2026-01-15T10:00:00Z",
    )


class TestConnectCommand:
    def test_connect_help(self, runner: CliRunner) -> None:
        result = runner.invoke(app, ["connect", "--help"])
        assert result.exit_code == 0
        assert "Connect the CLI to an existing Ilum installation" in result.output

    def test_connect_single_release_auto_detect(
        self, runner: CliRunner, mock_mgr: MagicMock
    ) -> None:
        info = _make_release_info()
        mock_mgr.scan_releases.return_value = [info]
        mock_mgr.get_release_info.return_value = info
        mock_mgr.fetch_computed_values.return_value = {
            "ilum-core": {"enabled": True},
            "ilum-ui": {"enabled": True},
        }
        mock_mgr.fetch_live_values.return_value = {
            "ilum-core": {"enabled": True},
            "ilum-ui": {"enabled": True},
        }
        mock_mgr.resolver.detect_enabled_modules = MagicMock(return_value=["core", "ui"])

        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"])

        assert result.exit_code == 0
        assert "Profile 'default' updated" in result.output
        assert "2 modules synced" in result.output
        assert "Values snapshot saved" in result.output
        mock_mgr.config_mgr.save.assert_called_once()

    def test_connect_multiple_releases_pick_first_with_yes(
        self, runner: CliRunner, mock_mgr: MagicMock
    ) -> None:
        releases = [
            _make_release_info(name="ilum", namespace="default"),
            _make_release_info(name="ilum-dev", namespace="dev", chart_version="6.6.0"),
        ]
        mock_mgr.scan_releases.return_value = releases
        mock_mgr.get_release_info.return_value = releases[0]
        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"])

        assert result.exit_code == 0
        assert "Auto-selected first release" in result.output
        assert "ilum" in result.output

    def test_connect_multiple_releases_user_picks(
        self, runner: CliRunner, mock_mgr: MagicMock
    ) -> None:
        releases = [
            _make_release_info(name="ilum", namespace="default"),
            _make_release_info(name="ilum-dev", namespace="dev"),
        ]
        mock_mgr.scan_releases.return_value = releases
        mock_mgr.get_release_info.return_value = releases[1]
        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"], input="2\n")

        assert result.exit_code == 0
        assert "ilum-dev" in result.output

    def test_connect_no_releases_found(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        mock_mgr.scan_releases.return_value = []

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

        assert result.exit_code == 1
        assert "No Ilum releases found" in result.output

    def test_connect_explicit_release_and_namespace(
        self, runner: CliRunner, mock_mgr: MagicMock
    ) -> None:
        info = _make_release_info(name="myrelease", namespace="prod")
        mock_mgr.get_release_info.return_value = info
        mock_mgr.fetch_computed_values.return_value = {}
        mock_mgr.resolver.detect_enabled_modules = MagicMock(return_value=["core"])

        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", "-r", "myrelease", "-n", "prod"],
            )

        assert result.exit_code == 0
        assert "myrelease" in result.output
        assert "prod" in result.output

    def test_connect_explicit_release_not_found(
        self, runner: CliRunner, mock_mgr: MagicMock
    ) -> None:
        mock_mgr.get_release_info.side_effect = ReleaseNotFoundError("Release 'bad' not found.")

        with patch("ilum.cli.connect_cmd._build_manager", return_value=mock_mgr):
            result = runner.invoke(
                app,
                ["connect", "--yes", "-r", "bad", "-n", "default"],
            )

        assert result.exit_code == 1
        assert "not found" in result.output

    def test_connect_non_ilum_chart_rejected(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        info = ReleaseInfo(
            name="nginx",
            namespace="default",
            status="deployed",
            chart="nginx",
            chart_version="1.0.0",
            revision=1,
            last_deployed="",
        )
        mock_mgr.get_release_info.return_value = info

        with patch("ilum.cli.connect_cmd._build_manager", return_value=mock_mgr):
            result = runner.invoke(
                app,
                ["connect", "--yes", "-r", "nginx", "-n", "default"],
            )

        assert result.exit_code == 1
        assert "not an Ilum chart" in result.output

    def test_connect_profile_already_connected_overwrite_with_yes(
        self, runner: CliRunner, mock_mgr: MagicMock
    ) -> None:
        info = _make_release_info()
        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=[])
        mock_mgr.config_mgr.load.return_value = IlumConfig(
            active_profile="default",
            profiles={
                "default": ProfileConfig(
                    name="default",
                    release_name="old-release",
                    cluster=ClusterConfig(namespace="old-ns"),
                )
            },
        )

        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"])

        assert result.exit_code == 0
        assert "Profile 'default' updated" in result.output

    def test_connect_profile_already_connected_abort(
        self, runner: CliRunner, mock_mgr: MagicMock
    ) -> None:
        info = _make_release_info()
        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=[])
        mock_mgr.config_mgr.load.return_value = IlumConfig(
            active_profile="default",
            profiles={
                "default": ProfileConfig(
                    name="default",
                    release_name="old-release",
                    cluster=ClusterConfig(namespace="old-ns"),
                )
            },
        )

        with patch("ilum.cli.connect_cmd._build_manager", return_value=mock_mgr):
            # Confirm the release (y), then reject overwrite (n)
            result = runner.invoke(app, ["connect"], input="y\nn\n")

        assert "Aborted" in result.output

    def test_connect_modules_synced_from_live_values(
        self, runner: CliRunner, mock_mgr: MagicMock
    ) -> None:
        info = _make_release_info()
        mock_mgr.scan_releases.return_value = [info]
        mock_mgr.get_release_info.return_value = info
        mock_mgr.fetch_computed_values.return_value = {
            "ilum-core": {"enabled": True},
            "ilum-ui": {"enabled": True},
            "ilum-sql": {"enabled": True},
        }
        mock_mgr.fetch_live_values.return_value = {
            "ilum-core": {"enabled": True},
            "ilum-ui": {"enabled": True},
            "ilum-sql": {"enabled": True},
        }
        mock_mgr.resolver.detect_enabled_modules = MagicMock(return_value=["core", "sql", "ui"])

        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"])

        assert result.exit_code == 0
        assert "3 modules synced" in result.output
        # Verify the config was saved with modules
        saved_config = mock_mgr.config_mgr.save.call_args[0][0]
        assert sorted(saved_config.profiles["default"].enabled_modules) == [
            "core",
            "sql",
            "ui",
        ]

    def test_connect_values_snapshot_saved(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        info = _make_release_info()
        mock_mgr.scan_releases.return_value = [info]
        mock_mgr.get_release_info.return_value = info
        mock_mgr.fetch_computed_values.return_value = {"some": "values"}
        mock_mgr.fetch_live_values.return_value = {"some": "values"}
        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") as mock_save,
        ):
            result = runner.invoke(app, ["connect", "--yes"])

        assert result.exit_code == 0
        assert "Values snapshot saved" in result.output
        mock_save.assert_called_once()
        snapshot = mock_save.call_args[0][0]
        assert snapshot.release == "ilum"
        assert snapshot.operation == "connect"

    def test_connect_snapshot_uses_user_supplied_values(
        self, runner: CliRunner, mock_mgr: MagicMock
    ) -> None:
        info = _make_release_info()
        mock_mgr.scan_releases.return_value = [info]
        mock_mgr.get_release_info.return_value = info
        mock_mgr.fetch_computed_values.return_value = {
            "ilum-core": {"enabled": True},
            "defaults": {"key": "val"},
        }
        mock_mgr.fetch_live_values.return_value = {"ilum-core": {"enabled": True}}
        mock_mgr.resolver.detect_enabled_modules = MagicMock(return_value=["core"])

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

        assert result.exit_code == 0
        snapshot = mock_save.call_args[0][0]
        # Snapshot should have user-supplied values, NOT computed values
        assert snapshot.values == {"ilum-core": {"enabled": True}}
        assert "defaults" not in snapshot.values

    def test_connect_stuck_release_shows_warning(
        self, runner: CliRunner, mock_mgr: MagicMock
    ) -> None:
        info = _make_release_info(status="pending-upgrade")
        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"])

        assert result.exit_code == 0
        assert "pending-upgrade" in result.output
        assert "force-rollback" in result.output

    def test_connect_cluster_unreachable(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        mock_mgr.scan_releases.side_effect = ClusterConnectionError(
            "Cannot connect to cluster",
            suggestion="Check your kubeconfig.",
        )

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

        assert result.exit_code == 1

    def test_connect_creates_new_profile(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        info = _make_release_info()
        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", "--profile", "staging"])

        assert result.exit_code == 0
        assert "staging" in result.output
        saved_config = mock_mgr.config_mgr.save.call_args[0][0]
        assert "staging" in saved_config.profiles
        assert saved_config.active_profile == "staging"

    def test_connect_shows_next_steps(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        info = _make_release_info()
        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"])

        assert result.exit_code == 0
        assert "ilum status" in result.output
        assert "ilum upgrade" in result.output
        assert "ilum module enable" in result.output
