"""Tests for ``ilum deps`` subcommands."""

from __future__ import annotations

import subprocess
from unittest.mock import patch

import pytest
from typer.testing import CliRunner

from ilum.cli.main import app
from ilum.doctor.checks import CheckResult, CheckStatus


def _ok(name: str) -> CheckResult:
    return CheckResult(name=name, status=CheckStatus.OK, message=f"{name} 1.0.0")


def _fail(name: str) -> CheckResult:
    return CheckResult(name=name, status=CheckStatus.FAIL, message=f"{name} not found on PATH")


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


class TestDepsInstall:
    def test_installs_missing_helm(self, runner: CliRunner) -> None:
        """Missing helm -> _install_tool called."""
        with (
            patch("ilum.cli.deps_cmd.check_binary") as mock_check,
            patch("ilum.cli.deps_cmd._install_tool") as mock_install,
            patch("ilum.cli.deps_cmd._detect_platform", return_value="linux"),
        ):
            # First call: fail (missing). Second call after install: ok.
            mock_check.side_effect = [_fail("helm"), _ok("helm")]
            result = runner.invoke(app, ["deps", "install", "helm"])
            assert result.exit_code == 0
            mock_install.assert_called_once()
            args = mock_install.call_args[0]
            assert args[0] == "helm"
            assert args[1] == "linux"

    def test_skips_installed_tool(self, runner: CliRunner) -> None:
        """Tool already OK -> skip, no install."""
        with (
            patch("ilum.cli.deps_cmd.check_binary", return_value=_ok("helm")),
            patch("ilum.cli.deps_cmd._install_tool") as mock_install,
        ):
            result = runner.invoke(app, ["deps", "install", "helm"])
            assert result.exit_code == 0
            mock_install.assert_not_called()
            assert "already installed" in result.output

    def test_dry_run_no_install(self, runner: CliRunner) -> None:
        """--dry-run -> shows preview, no _install_tool calls."""
        with (
            patch("ilum.cli.deps_cmd.check_binary", return_value=_fail("helm")),
            patch("ilum.cli.deps_cmd._install_tool") as mock_install,
        ):
            result = runner.invoke(app, ["deps", "install", "helm", "--dry-run"])
            assert result.exit_code == 0
            mock_install.assert_not_called()
            assert "Would install" in result.output
            assert "Dry-run" in result.output

    def test_specific_tools(self, runner: CliRunner) -> None:
        """ilum deps install helm kubectl -> only those two."""
        calls: list[str] = []

        def fake_check(name, min_ver=None):
            return _fail(name)

        def fake_install(name, plat, console):
            calls.append(name)

        with (
            patch("ilum.cli.deps_cmd.check_binary", side_effect=fake_check),
            patch("ilum.cli.deps_cmd._install_tool", side_effect=fake_install),
            patch("ilum.cli.deps_cmd._detect_platform", return_value="linux"),
        ):
            runner.invoke(app, ["deps", "install", "helm", "kubectl"])
            # Exit code 1 because recheck will also fail (fake_check always returns fail)
            assert "helm" in calls
            assert "kubectl" in calls
            assert "docker" not in calls
            assert "k3d" not in calls

    def test_invalid_tool_name(self, runner: CliRunner) -> None:
        """Unknown tool name -> error."""
        result = runner.invoke(app, ["deps", "install", "foobar"])
        assert result.exit_code == 1
        assert "Unknown tool" in result.output

    def test_invalid_provider(self, runner: CliRunner) -> None:
        """Unknown provider -> error."""
        result = runner.invoke(app, ["deps", "install", "--provider", "badprov"])
        assert result.exit_code == 1
        assert "Unknown provider" in result.output

    def test_provider_flag(self, runner: CliRunner) -> None:
        """--provider kind -> installs kind instead of k3d."""

        def fake_check(name, min_ver=None):
            return _ok(name)  # all already installed

        with (
            patch("ilum.cli.deps_cmd.check_binary", side_effect=fake_check),
            patch("ilum.cli.deps_cmd._install_tool") as mock_install,
        ):
            result = runner.invoke(app, ["deps", "install", "--provider", "kind"])
            assert result.exit_code == 0
            # All skipped since they're OK, but "kind" should be in the target list
            assert "kind" in result.output
            # k3d should NOT be in the output since we picked kind
            mock_install.assert_not_called()

    def test_install_failure_continues(self, runner: CliRunner) -> None:
        """One tool fails -> others still attempted."""
        call_count = {"helm": 0, "kubectl": 0}

        def fake_check(name, min_ver=None):
            return _fail(name)

        def fake_install(name, plat, console):
            call_count[name] = call_count.get(name, 0) + 1
            if name == "helm":
                raise subprocess.CalledProcessError(1, "helm")

        with (
            patch("ilum.cli.deps_cmd.check_binary", side_effect=fake_check),
            patch("ilum.cli.deps_cmd._install_tool", side_effect=fake_install),
            patch("ilum.cli.deps_cmd._detect_platform", return_value="linux"),
        ):
            result = runner.invoke(app, ["deps", "install", "helm", "kubectl"])
            assert result.exit_code == 1  # at least one failure
            assert call_count["helm"] == 1
            assert call_count["kubectl"] == 1
            assert "failed" in result.output.lower()

    def test_default_installs_core_plus_provider(self, runner: CliRunner) -> None:
        """No args -> helm, kubectl, docker, k3d."""
        checked: list[str] = []

        def fake_check(name, min_ver=None):
            checked.append(name)
            return _ok(name)

        with (
            patch("ilum.cli.deps_cmd.check_binary", side_effect=fake_check),
            patch("ilum.cli.deps_cmd._install_tool") as mock_install,
        ):
            result = runner.invoke(app, ["deps", "install"])
            assert result.exit_code == 0
            assert checked == ["helm", "kubectl", "docker", "k3d"]
            mock_install.assert_not_called()


class TestDepsList:
    def test_shows_all_tools(self, runner: CliRunner) -> None:
        """Lists all 6 tools with status."""
        with patch("ilum.cli.deps_cmd.check_binary", return_value=_ok("test")):
            result = runner.invoke(app, ["deps", "list"])
            assert result.exit_code == 0
            for name in ("helm", "kubectl", "docker", "k3d", "minikube", "kind"):
                assert name in result.output

    def test_shows_installed_version(self, runner: CliRunner) -> None:
        """Installed tool shows version and OK."""
        with (
            patch("ilum.cli.deps_cmd.check_binary", return_value=_ok("helm")),
            patch("ilum.cli.deps_cmd.shutil.which", return_value="/usr/local/bin/helm"),
        ):
            result = runner.invoke(app, ["deps", "list"])
            assert result.exit_code == 0
            assert "OK" in result.output

    def test_shows_missing_tool(self, runner: CliRunner) -> None:
        """Missing tool shows 'Missing'."""
        with (
            patch("ilum.cli.deps_cmd.check_binary", return_value=_fail("helm")),
            patch("ilum.cli.deps_cmd.shutil.which", return_value=None),
        ):
            result = runner.invoke(app, ["deps", "list"])
            assert result.exit_code == 0
            assert "Missing" in result.output

    def test_shows_outdated_tool(self, runner: CliRunner) -> None:
        """Outdated tool shows 'Outdated'."""
        outdated = CheckResult(
            name="helm",
            status=CheckStatus.FAIL,
            message="helm version 3.10.0 < required 3.12",
        )
        with (
            patch("ilum.cli.deps_cmd.check_binary", return_value=outdated),
            patch("ilum.cli.deps_cmd.shutil.which", return_value="/usr/local/bin/helm"),
        ):
            result = runner.invoke(app, ["deps", "list"])
            assert result.exit_code == 0
            assert "Outdated" in result.output
