"""Tests for ``ilum quickstart`` 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 IlumConfig, ProfileConfig
from ilum.errors import ClusterConnectionError, PrerequisiteError


@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.k8s = MagicMock()
    mgr.k8s.namespace_exists.return_value = True
    mgr.plan_install.return_value = MagicMock(
        release="ilum",
        namespace="default",
        chart="ilum/ilum",
        chart_version="",
        modules=["core", "ui"],
        atomic=True,
        set_flags=[],
        warnings=[],
    )
    mgr.preview_command.return_value = ["helm", "install", "ilum", "ilum/ilum"]
    return mgr


def _patch_all(mock_mgr: MagicMock, cluster_reachable: str = "minikube"):
    """Return a context manager that patches all external dependencies."""
    from contextlib import ExitStack

    stack = ExitStack()

    stack.enter_context(
        patch("ilum.cli.quickstart_cmd._cluster_reachable", return_value=cluster_reachable)
    )
    stack.enter_context(patch("ilum.cli.quickstart_cmd._build_manager", return_value=mock_mgr))
    stack.enter_context(patch("ilum.cli.quickstart_cmd.ToolInstaller"))
    stack.enter_context(patch("ilum.cli.quickstart_cmd.ConfigManager"))
    stack.enter_context(patch("ilum.cli.quickstart_cmd.IlumPaths"))
    stack.enter_context(patch("ilum.cli.install_cmd._resolve_nodeport_conflicts"))

    return stack


@pytest.fixture()
def _patch_all_ctx(mock_mgr: MagicMock):
    """Fixture variant of _patch_all that auto-enters/exits the context manager."""
    with _patch_all(mock_mgr):
        yield


class TestQuickstartCommand:
    def test_quickstart_help(self, runner: CliRunner) -> None:
        result = runner.invoke(app, ["quickstart", "--help"])
        assert result.exit_code == 0
        assert "Install Ilum in one command" in result.output

    def test_quickstart_with_reachable_cluster(
        self, runner: CliRunner, mock_mgr: MagicMock
    ) -> None:
        with _patch_all(mock_mgr, cluster_reachable="minikube"):
            result = runner.invoke(app, ["quickstart"])

        assert result.exit_code == 0
        assert "Cluster reachable" in result.output
        assert "Ilum installed successfully" in result.output
        mgr_call = mock_mgr.plan_install
        mgr_call.assert_called_once()
        mock_mgr.execute.assert_called_once()

    def test_quickstart_creates_cluster_when_none_reachable(
        self, runner: CliRunner, mock_mgr: MagicMock
    ) -> None:
        from ilum.config.models import ClusterRecord

        mock_record = ClusterRecord(
            name="ilum-dev",
            provider="minikube",
            kubecontext="ilum-dev",
            created_at="2026-01-01T00:00:00Z",
        )

        with (
            patch("ilum.cli.quickstart_cmd._cluster_reachable", return_value=""),
            patch("ilum.cli.quickstart_cmd._build_manager", return_value=mock_mgr),
            patch("ilum.cli.quickstart_cmd.ToolInstaller"),
            patch("ilum.cli.quickstart_cmd.ConfigManager") as mock_cfg_cls,
            patch("ilum.cli.quickstart_cmd.IlumPaths"),
            patch("ilum.cli.quickstart_cmd.ClusterManager") as mock_cm_cls,
            patch("ilum.cli.install_cmd._resolve_nodeport_conflicts"),
        ):
            mock_cm = mock_cm_cls.return_value
            mock_cm.create.return_value = mock_record
            mock_cfg = mock_cfg_cls.return_value
            mock_cfg.ensure_config.return_value = IlumConfig(
                active_profile="default",
                profiles={"default": ProfileConfig(name="default")},
            )

            result = runner.invoke(app, ["quickstart"])

        assert result.exit_code == 0
        assert "No reachable cluster found" in result.output
        assert "Creating a local cluster" in result.output
        mock_cm.create.assert_called_once()
        # Verify minikube was used as default provider
        call_args = mock_cm.create.call_args
        assert call_args[0][0].value == "minikube"

    def test_quickstart_creates_k3d_cluster_with_provider_flag(
        self, runner: CliRunner, mock_mgr: MagicMock
    ) -> None:
        from ilum.config.models import ClusterRecord

        mock_record = ClusterRecord(
            name="ilum-dev",
            provider="k3d",
            kubecontext="k3d-ilum-dev",
            created_at="2026-01-01T00:00:00Z",
        )

        with (
            patch("ilum.cli.quickstart_cmd._cluster_reachable", return_value=""),
            patch("ilum.cli.quickstart_cmd._build_manager", return_value=mock_mgr),
            patch("ilum.cli.quickstart_cmd.ToolInstaller"),
            patch("ilum.cli.quickstart_cmd.ConfigManager") as mock_cfg_cls,
            patch("ilum.cli.quickstart_cmd.IlumPaths"),
            patch("ilum.cli.quickstart_cmd.ClusterManager") as mock_cm_cls,
            patch("ilum.cli.install_cmd._resolve_nodeport_conflicts"),
        ):
            mock_cm = mock_cm_cls.return_value
            mock_cm.create.return_value = mock_record
            mock_cfg = mock_cfg_cls.return_value
            mock_cfg.ensure_config.return_value = IlumConfig(
                active_profile="default",
                profiles={"default": ProfileConfig(name="default")},
            )

            result = runner.invoke(app, ["quickstart", "--provider", "k3d"])

        assert result.exit_code == 0
        call_args = mock_cm.create.call_args
        assert call_args[0][0].value == "k3d"

    def test_quickstart_cluster_creation_failure_exits(
        self, runner: CliRunner, mock_mgr: MagicMock
    ) -> None:
        with (
            patch("ilum.cli.quickstart_cmd._cluster_reachable", return_value=""),
            patch("ilum.cli.quickstart_cmd._build_manager", return_value=mock_mgr),
            patch("ilum.cli.quickstart_cmd.ToolInstaller"),
            patch("ilum.cli.quickstart_cmd.ConfigManager") as mock_cfg_cls,
            patch("ilum.cli.quickstart_cmd.IlumPaths"),
            patch("ilum.cli.quickstart_cmd.ClusterManager") as mock_cm_cls,
            patch("ilum.cli.install_cmd._resolve_nodeport_conflicts"),
        ):
            mock_cm = mock_cm_cls.return_value
            mock_cm.create.side_effect = PrerequisiteError("minikube not found on PATH")
            mock_cfg = mock_cfg_cls.return_value
            mock_cfg.ensure_config.return_value = IlumConfig(
                active_profile="default",
                profiles={"default": ProfileConfig(name="default")},
            )

            result = runner.invoke(app, ["quickstart"])

        assert result.exit_code == 1
        assert "minikube not found on PATH" in result.output

    def test_quickstart_with_extra_modules(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        with _patch_all(mock_mgr, cluster_reachable="minikube"):
            result = runner.invoke(app, ["quickstart", "-m", "sql", "-m", "jupyter"])

        assert result.exit_code == 0
        assert "Ilum installed successfully" in result.output
        # Verify modules were passed to plan_install
        plan_call = mock_mgr.plan_install.call_args
        modules_arg = plan_call[1]["modules"]
        assert "sql" in modules_arg
        assert "jupyter" in modules_arg

    def test_quickstart_with_custom_profile(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        with _patch_all(mock_mgr, cluster_reachable="minikube"):
            result = runner.invoke(app, ["quickstart", "--profile", "staging"])

        assert result.exit_code == 0
        assert "staging" in result.output
        assert "Ilum installed successfully" in result.output

    def test_quickstart_shows_next_steps(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        with _patch_all(mock_mgr, cluster_reachable="minikube"):
            result = runner.invoke(app, ["quickstart"])

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

    def test_quickstart_shows_command_preview(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        with _patch_all(mock_mgr, cluster_reachable="minikube"):
            result = runner.invoke(app, ["quickstart"])

        assert result.exit_code == 0
        mock_mgr.preview_command.assert_called_once()

    def test_quickstart_installs_without_atomic(
        self, runner: CliRunner, mock_mgr: MagicMock
    ) -> None:
        with _patch_all(mock_mgr, cluster_reachable="minikube"):
            result = runner.invoke(app, ["quickstart"])

        assert result.exit_code == 0
        plan_call = mock_mgr.plan_install.call_args
        assert plan_call[1]["atomic"] is False

    def test_quickstart_helm_install_failure(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        mock_mgr.execute.side_effect = ClusterConnectionError("Cannot connect to cluster")

        with _patch_all(mock_mgr, cluster_reachable="minikube"):
            result = runner.invoke(app, ["quickstart"])

        assert result.exit_code == 1

    def test_quickstart_creates_namespace_if_missing(
        self, runner: CliRunner, mock_mgr: MagicMock
    ) -> None:
        mock_mgr.k8s.namespace_exists.return_value = False

        with _patch_all(mock_mgr, cluster_reachable="minikube"):
            result = runner.invoke(app, ["quickstart"])

        assert result.exit_code == 0
        mock_mgr.k8s.create_namespace.assert_called_once_with("default")

    def test_quickstart_uses_dev_preset(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        from ilum.config.models import ClusterRecord

        mock_record = ClusterRecord(
            name="ilum-dev",
            provider="minikube",
            kubecontext="ilum-dev",
            created_at="2026-01-01T00:00:00Z",
        )

        with (
            patch("ilum.cli.quickstart_cmd._cluster_reachable", return_value=""),
            patch("ilum.cli.quickstart_cmd._build_manager", return_value=mock_mgr),
            patch("ilum.cli.quickstart_cmd.ToolInstaller"),
            patch("ilum.cli.quickstart_cmd.ConfigManager") as mock_cfg_cls,
            patch("ilum.cli.quickstart_cmd.IlumPaths"),
            patch("ilum.cli.quickstart_cmd.ClusterManager") as mock_cm_cls,
            patch("ilum.cli.install_cmd._resolve_nodeport_conflicts"),
        ):
            mock_cm = mock_cm_cls.return_value
            mock_cm.create.return_value = mock_record
            mock_cfg = mock_cfg_cls.return_value
            mock_cfg.ensure_config.return_value = IlumConfig(
                active_profile="default",
                profiles={"default": ProfileConfig(name="default")},
            )

            result = runner.invoke(app, ["quickstart"])

        assert result.exit_code == 0
        call_args = mock_cm.create.call_args
        preset_arg = call_args[0][1]
        assert preset_arg.cpus == 6
        assert preset_arg.memory == "12g"
        assert preset_arg.name == "ilum-dev"

    def test_quickstart_saves_modules_after_install(
        self, runner: CliRunner, mock_mgr: MagicMock
    ) -> None:
        with _patch_all(mock_mgr, cluster_reachable="minikube"):
            result = runner.invoke(app, ["quickstart"])

        assert result.exit_code == 0
        mock_mgr.save_enabled_modules.assert_called_once()

    def test_quickstart_custom_timeout(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        with (
            patch("ilum.cli.quickstart_cmd._cluster_reachable", return_value="minikube"),
            patch("ilum.cli.quickstart_cmd._build_manager", return_value=mock_mgr) as mock_build,
            patch("ilum.cli.quickstart_cmd.ToolInstaller"),
            patch("ilum.cli.quickstart_cmd.ConfigManager") as mock_cfg_cls,
            patch("ilum.cli.quickstart_cmd.IlumPaths"),
            patch("ilum.cli.install_cmd._resolve_nodeport_conflicts"),
        ):
            mock_cfg = mock_cfg_cls.return_value
            mock_cfg.ensure_config.return_value = IlumConfig(
                active_profile="default",
                profiles={"default": ProfileConfig(name="default")},
            )

            result = runner.invoke(app, ["quickstart", "--timeout", "20m"])

        assert result.exit_code == 0
        mock_build.assert_called_once()
        assert mock_build.call_args[0][2] == "20m"


class TestQuickstartPreset:
    def test_quickstart_with_preset(self, runner, _patch_all_ctx, mock_mgr):
        result = runner.invoke(app, ["quickstart", "--preset", "production"])
        assert result.exit_code == 0
        assert "installed successfully" in result.output
        call_kwargs = mock_mgr.plan_install.call_args.kwargs
        assert "monitoring" in call_kwargs["modules"]

    def test_quickstart_preset_plus_extra_module(self, runner, _patch_all_ctx, mock_mgr):
        result = runner.invoke(app, ["quickstart", "--preset", "development", "-m", "sql"])
        assert result.exit_code == 0
        call_kwargs = mock_mgr.plan_install.call_args.kwargs
        assert "sql" in call_kwargs["modules"]
        assert "core" in call_kwargs["modules"]

    def test_quickstart_preset_applies_set_flags(self, runner, _patch_all_ctx, mock_mgr):
        result = runner.invoke(app, ["quickstart", "--preset", "production"])
        assert result.exit_code == 0
        call_kwargs = mock_mgr.plan_install.call_args.kwargs
        flags = call_kwargs["set_flags"]
        assert any("global.security.enabled=true" in f for f in (flags or []))

    def test_quickstart_invalid_preset(self, runner, _patch_all_ctx, mock_mgr):
        result = runner.invoke(app, ["quickstart", "--preset", "nonexistent"])
        assert result.exit_code == 1
        assert "Unknown preset" in result.output
