"""Tests that CLI commands use the module-level console singleton.

Bug: Commands created fresh ``IlumConsole()`` instances, silently ignoring
``--quiet`` and ``--output`` flags set on the singleton by ``main()``.
Fix: All commands now reference ``output_mod.console``.
"""

from __future__ import annotations

from unittest.mock import MagicMock, patch


class TestCommandsUseSingleton:
    """Verify each command file references output_mod.console, not IlumConsole()."""

    def test_doctor_uses_singleton(self) -> None:
        """doctor_cmd imports output_mod and uses .console."""
        import ilum.cli.doctor_cmd as mod

        assert hasattr(mod, "output_mod") or "output_mod" in dir(mod)

    def test_install_uses_singleton(self) -> None:
        import ilum.cli.install_cmd as mod

        assert hasattr(mod, "output_mod") or "output_mod" in dir(mod)

    def test_upgrade_uses_singleton(self) -> None:
        import ilum.cli.upgrade_cmd as mod

        assert hasattr(mod, "output_mod") or "output_mod" in dir(mod)

    def test_uninstall_uses_singleton(self) -> None:
        import ilum.cli.uninstall_cmd as mod

        assert hasattr(mod, "output_mod") or "output_mod" in dir(mod)

    def test_connect_uses_singleton(self) -> None:
        import ilum.cli.connect_cmd as mod

        assert hasattr(mod, "output_mod") or "output_mod" in dir(mod)

    def test_quickstart_uses_singleton(self) -> None:
        import ilum.cli.quickstart_cmd as mod

        assert hasattr(mod, "output_mod") or "output_mod" in dir(mod)

    def test_logs_uses_singleton(self) -> None:
        import ilum.cli.logs_cmd as mod

        assert hasattr(mod, "output_mod") or "output_mod" in dir(mod)

    def test_config_uses_singleton(self) -> None:
        import ilum.cli.config_cmd as mod

        assert hasattr(mod, "output_mod") or "output_mod" in dir(mod)

    def test_module_uses_singleton(self) -> None:
        import ilum.cli.module_cmd as mod

        assert hasattr(mod, "output_mod") or "output_mod" in dir(mod)

    def test_preset_uses_singleton(self) -> None:
        import ilum.cli.preset_cmd as mod

        assert hasattr(mod, "output_mod") or "output_mod" in dir(mod)

    def test_airgap_uses_singleton(self) -> None:
        import ilum.cli.airgap_cmd as mod

        assert hasattr(mod, "output_mod") or "output_mod" in dir(mod)


class TestQuietFlagPropagatesToCommands:
    """Verify that --quiet set via main callback is visible inside commands."""

    def test_quiet_propagates_to_config_show(self, tmp_config_dir, capsys) -> None:
        from typer.testing import CliRunner

        from ilum.cli.main import app
        from ilum.config.manager import ConfigManager

        # Create a config so `config show` has something to display
        ConfigManager(tmp_config_dir).ensure_config()

        runner = CliRunner()
        result = runner.invoke(app, ["--quiet", "config", "show"])

        # In quiet mode, info/success messages are suppressed but the command
        # should still run without error.
        assert result.exit_code == 0

    def test_quiet_propagates_to_module_list(self, capsys) -> None:
        from typer.testing import CliRunner

        from ilum.cli.main import app

        runner = CliRunner()
        result = runner.invoke(app, ["--quiet", "module", "list"])

        assert result.exit_code == 0

    def test_output_json_propagates_to_module_list(self) -> None:
        from typer.testing import CliRunner

        from ilum.cli.main import app

        runner = CliRunner()
        result = runner.invoke(app, ["--output", "json", "module", "list"])

        assert result.exit_code == 0
        # JSON output should be parseable
        import json

        data = json.loads(result.stdout)
        assert isinstance(data, list)
        assert len(data) > 0
        assert "name" in data[0]


class TestNoFreshIlumConsoleInCommands:
    """Grep-style regression test: no command file should call IlumConsole()."""

    def test_no_fresh_console_in_command_files(self) -> None:
        """Scan all *_cmd.py files for IlumConsole() instantiation."""
        from pathlib import Path

        cli_dir = Path(__file__).resolve().parent.parent.parent / "src" / "ilum" / "cli"
        violations = []

        for cmd_file in sorted(cli_dir.glob("*_cmd.py")):
            source = cmd_file.read_text()
            # Look for `= IlumConsole()` which creates a fresh instance
            for i, line in enumerate(source.splitlines(), 1):
                stripped = line.strip()
                if "= IlumConsole()" in stripped:
                    violations.append(f"{cmd_file.name}:{i}: {stripped}")

        assert violations == [], (
            "Command files must use output_mod.console, not IlumConsole():\n"
            + "\n".join(violations)
        )


class TestDoctorResourceCheckUsesProfileModules:
    """Bug: DoctorRunner was created without modules, so resource estimate was 0."""

    def test_doctor_passes_modules_to_runner(self, tmp_config_dir) -> None:
        """doctor_cmd loads enabled_modules from active profile and passes to DoctorRunner."""
        from ilum.config.manager import ConfigManager
        from ilum.config.models import IlumConfig, ProfileConfig

        # Set up a profile with enabled modules
        mgr = ConfigManager(tmp_config_dir)
        config = IlumConfig(
            active_profile="test",
            profiles={
                "test": ProfileConfig(
                    name="test",
                    enabled_modules=["core", "ui", "mongodb", "jupyter"],
                ),
            },
        )
        mgr.save(config)

        with patch("ilum.cli.doctor_cmd.DoctorRunner") as mock_runner_cls:
            mock_instance = MagicMock()
            mock_instance.run_all.return_value = []
            mock_runner_cls.return_value = mock_instance

            with (
                patch("ilum.cli.doctor_cmd.HelmClient"),
                patch("ilum.cli.doctor_cmd.KubeClient"),
            ):
                from typer.testing import CliRunner

                from ilum.cli.main import app

                runner = CliRunner()
                runner.invoke(app, ["doctor"])

            # Check that DoctorRunner was called with modules from the profile
            call_kwargs = mock_runner_cls.call_args
            if call_kwargs:
                modules_arg = call_kwargs.kwargs.get("modules", call_kwargs[1].get("modules", []))
                assert "core" in modules_arg
                assert "ui" in modules_arg
                assert "mongodb" in modules_arg
                assert "jupyter" in modules_arg

    def test_doctor_handles_missing_profile_gracefully(self, tmp_config_dir) -> None:
        """If no profile exists, modules should default to empty list (no crash)."""
        with patch("ilum.cli.doctor_cmd.DoctorRunner") as mock_runner_cls:
            mock_instance = MagicMock()
            mock_instance.run_all.return_value = []
            mock_runner_cls.return_value = mock_instance

            with (
                patch("ilum.cli.doctor_cmd.HelmClient"),
                patch("ilum.cli.doctor_cmd.KubeClient"),
            ):
                from typer.testing import CliRunner

                from ilum.cli.main import app

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

            # Should not crash — modules defaults to []
            assert result.exit_code == 0

    def test_resource_check_nonzero_with_modules(self) -> None:
        """check_resources with actual modules should produce non-zero estimate."""
        from ilum.doctor.checks import CheckStatus, check_resources

        k8s = MagicMock()
        k8s.get_cluster_resources.return_value = {
            "allocatable_memory_bytes": 16 * 1024 * 1024 * 1024,
            "allocatable_cpu_millicores": 8000,
            "node_count": 2,
        }

        # With modules — should report actual resource usage
        result = check_resources(k8s, ["core", "ui", "mongodb", "jupyter"])
        assert result.status == CheckStatus.OK
        assert "0 MiB" not in result.message
        assert "0.0 CPU" not in result.message

    def test_resource_check_zero_without_modules(self) -> None:
        """check_resources with empty modules is the bug scenario — estimate is 0."""
        from ilum.doctor.checks import CheckStatus, check_resources

        k8s = MagicMock()
        k8s.get_cluster_resources.return_value = {
            "allocatable_memory_bytes": 16 * 1024 * 1024 * 1024,
            "allocatable_cpu_millicores": 8000,
            "node_count": 2,
        }

        result = check_resources(k8s, [])
        assert result.status == CheckStatus.OK
        # With no modules, estimate IS legitimately 0
        assert "0 MiB" in result.message or "0.0 CPU" in result.message
