"""Tests for ``ilum uninstall`` 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.core.helm import HelmResult
from ilum.errors import HelmError


@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.release_exists.return_value = True
    mgr.execute.return_value = HelmResult(
        returncode=0,
        stdout="uninstalled",
        stderr="",
        command=[],
    )
    mgr.k8s = MagicMock()
    mgr.k8s.delete_pvcs.return_value = ["pvc-1", "pvc-2"]
    return mgr


def _patch_uninstall(mock_mgr: MagicMock):
    return patch("ilum.cli.uninstall_cmd.ReleaseManager", return_value=mock_mgr)


class TestUninstallCommand:
    def test_uninstall_help(self, runner: CliRunner) -> None:
        result = runner.invoke(app, ["uninstall", "--help"])
        assert result.exit_code == 0
        assert "Uninstall the Ilum platform" in result.output

    def test_uninstall_with_yes(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        with _patch_uninstall(mock_mgr):
            result = runner.invoke(app, ["uninstall", "--yes"])
        assert result.exit_code == 0
        assert "uninstalled" in result.output
        mock_mgr.execute.assert_called_once()

    def test_uninstall_not_found_idempotent(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        mock_mgr.release_exists.return_value = False
        with _patch_uninstall(mock_mgr):
            result = runner.invoke(app, ["uninstall", "--yes"])
        assert result.exit_code == 0
        assert "nothing to uninstall" in result.output
        mock_mgr.execute.assert_not_called()

    def test_uninstall_aborted_by_user(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        with _patch_uninstall(mock_mgr):
            result = runner.invoke(app, ["uninstall"], input="n\n")
        assert "Aborted" in result.output
        mock_mgr.execute.assert_not_called()

    def test_uninstall_with_delete_data(
        self, runner: CliRunner, mock_mgr: MagicMock, tmp_config_dir
    ) -> None:
        with _patch_uninstall(mock_mgr):
            result = runner.invoke(app, ["uninstall", "--delete-data"], input="ilum\n")
        assert result.exit_code == 0
        mock_mgr.k8s.delete_pvcs.assert_called_once()

    def test_uninstall_delete_data_wrong_name(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        with _patch_uninstall(mock_mgr):
            result = runner.invoke(app, ["uninstall", "--delete-data"], input="wrong\n")
        assert result.exit_code == 1
        assert "does not match" in result.output
        mock_mgr.execute.assert_not_called()

    def test_uninstall_with_delete_namespace(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        with _patch_uninstall(mock_mgr):
            result = runner.invoke(
                app,
                ["uninstall", "--delete-namespace", "--yes"],
            )
        # With --yes but delete-namespace, it still needs typed confirmation
        # Actually with --yes we skip confirmation
        assert result.exit_code == 0
        mock_mgr.k8s.delete_namespace.assert_called_once()

    def test_uninstall_clears_config(
        self, runner: CliRunner, mock_mgr: MagicMock, tmp_config_dir
    ) -> None:
        with _patch_uninstall(mock_mgr):
            runner.invoke(app, ["uninstall", "--yes"])
        mock_mgr.save_enabled_modules.assert_called_once_with([], release="ilum")

    def test_uninstall_helm_error(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        mock_mgr.execute.side_effect = HelmError("uninstall failed")
        with _patch_uninstall(mock_mgr):
            result = runner.invoke(app, ["uninstall", "--yes"])
        assert result.exit_code == 1

    def test_uninstall_shows_summary_panel(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        with _patch_uninstall(mock_mgr):
            result = runner.invoke(app, ["uninstall", "--yes"])
        assert "Uninstall Summary" in result.output

    def test_uninstall_summary_shows_destructive_flags(
        self, runner: CliRunner, mock_mgr: MagicMock
    ) -> None:
        with _patch_uninstall(mock_mgr):
            result = runner.invoke(
                app,
                ["uninstall", "--delete-data", "--delete-namespace", "--yes"],
            )
        assert "Delete PVCs" in result.output
        assert "Yes" in result.output
        assert "Delete Namespace" in result.output

    def test_uninstall_dry_run(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        with _patch_uninstall(mock_mgr):
            result = runner.invoke(app, ["uninstall", "--dry-run"])
        assert result.exit_code == 0
        assert "Dry-run" in result.output
        assert "Command:" in result.output
        mock_mgr.execute.assert_not_called()

    def test_uninstall_dry_run_skips_pvc_and_namespace(
        self, runner: CliRunner, mock_mgr: MagicMock
    ) -> None:
        with _patch_uninstall(mock_mgr):
            result = runner.invoke(
                app,
                ["uninstall", "--dry-run", "--delete-data", "--delete-namespace"],
            )
        assert result.exit_code == 0
        assert "Dry-run" in result.output
        mock_mgr.execute.assert_not_called()
        mock_mgr.k8s.delete_pvcs.assert_not_called()
        mock_mgr.k8s.delete_namespace.assert_not_called()

    def test_uninstall_shows_command_preview(self, runner: CliRunner, mock_mgr: MagicMock) -> None:
        with _patch_uninstall(mock_mgr):
            result = runner.invoke(app, ["uninstall", "--yes"])
        assert "Command:" in result.output
