"""Tests for vr setup command."""

import platform
import pytest
from pathlib import Path
from unittest.mock import patch, Mock, MagicMock

from typer.testing import CliRunner

from vr_cli.cli import app
from vr_cli.commands.setup import (
    get_helm_install_command,
    install_helm,
    install_skills,
    setup,
    TARGET_PATHS,
)


runner = CliRunner()


@pytest.fixture(autouse=True)
def mock_save_setup_targets():
    """Mock save_setup_targets for all tests to avoid writing to real config."""
    with patch("vr_cli.commands.setup.save_setup_targets") as mock_save:
        yield mock_save


class TestGetHelmInstallCommand:
    """Tests for get_helm_install_command function."""

    def test_macos_with_homebrew(self):
        """Should return brew command on macOS with Homebrew."""
        with patch("platform.system") as mock_system, \
             patch("shutil.which") as mock_which:
            mock_system.return_value = "Darwin"
            mock_which.return_value = "/usr/local/bin/brew"

            command, message = get_helm_install_command()

        assert command == ["brew", "install", "helm"]
        assert "Homebrew" in message

    def test_macos_without_homebrew(self):
        """Should return error on macOS without Homebrew."""
        with patch("platform.system") as mock_system, \
             patch("shutil.which") as mock_which:
            mock_system.return_value = "Darwin"
            mock_which.return_value = None

            command, message = get_helm_install_command()

        assert command is None
        assert "Homebrew" in message

    def test_linux_with_snap(self):
        """Should return snap command on Linux with Snap."""
        with patch("platform.system") as mock_system, \
             patch("shutil.which") as mock_which:
            mock_system.return_value = "Linux"
            mock_which.side_effect = lambda cmd: "/usr/bin/snap" if cmd == "snap" else None

            command, message = get_helm_install_command()

        assert command is not None
        assert "snap" in command
        assert "Snap" in message

    def test_linux_with_apt(self):
        """Should return script command on Linux with apt."""
        with patch("platform.system") as mock_system, \
             patch("shutil.which") as mock_which:
            mock_system.return_value = "Linux"
            mock_which.side_effect = lambda cmd: "/usr/bin/apt-get" if cmd == "apt-get" else None

            command, message = get_helm_install_command()

        assert command is not None
        assert "bash" in command
        assert "script" in message.lower()

    def test_linux_fallback(self):
        """Should use official script as fallback on Linux."""
        with patch("platform.system") as mock_system, \
             patch("shutil.which") as mock_which:
            mock_system.return_value = "Linux"
            mock_which.return_value = None

            command, message = get_helm_install_command()

        assert command is not None
        assert "bash" in command

    def test_windows_with_chocolatey(self):
        """Should return choco command on Windows with Chocolatey."""
        with patch("platform.system") as mock_system, \
             patch("shutil.which") as mock_which:
            mock_system.return_value = "Windows"
            mock_which.side_effect = lambda cmd: "C:\\ProgramData\\chocolatey\\bin\\choco.exe" if cmd == "choco" else None

            command, message = get_helm_install_command()

        assert command is not None
        assert "choco" in command
        assert "Chocolatey" in message

    def test_windows_with_scoop(self):
        """Should return scoop command on Windows with Scoop."""
        with patch("platform.system") as mock_system, \
             patch("shutil.which") as mock_which:
            mock_system.return_value = "Windows"

            def which_side_effect(cmd):
                if cmd == "choco":
                    return None
                if cmd == "scoop":
                    return "C:\\Users\\test\\scoop\\shims\\scoop"
                return None

            mock_which.side_effect = which_side_effect

            command, message = get_helm_install_command()

        assert command is not None
        assert "scoop" in command
        assert "Scoop" in message

    def test_windows_no_package_manager(self):
        """Should return error on Windows without package manager."""
        with patch("platform.system") as mock_system, \
             patch("shutil.which") as mock_which:
            mock_system.return_value = "Windows"
            mock_which.return_value = None

            command, message = get_helm_install_command()

        assert command is None
        assert "Chocolatey" in message or "Scoop" in message

    def test_unsupported_platform(self):
        """Should return error for unsupported platform."""
        with patch("platform.system") as mock_system:
            mock_system.return_value = "UnknownOS"

            command, message = get_helm_install_command()

        assert command is None
        assert "Unsupported" in message


class TestInstallHelm:
    """Tests for install_helm function."""

    def test_helm_already_installed(self):
        """Should succeed if Helm is already installed."""
        with patch("vr_cli.commands.setup.is_helm_available") as mock_available, \
             patch("subprocess.run") as mock_run, \
             patch("vr_cli.commands.setup.print_success"), \
             patch("vr_cli.commands.setup.print_info"):
            mock_available.return_value = True
            mock_run.return_value = Mock(stdout="v3.14.0", returncode=0)

            result = install_helm()

        assert result is True

    def test_helm_install_success(self):
        """Should install Helm successfully."""
        with patch("vr_cli.commands.setup.is_helm_available") as mock_available, \
             patch("vr_cli.commands.setup.get_helm_install_command") as mock_cmd, \
             patch("subprocess.run") as mock_run, \
             patch("vr_cli.commands.setup.print_success"), \
             patch("vr_cli.commands.setup.print_info"):

            # First call returns False (not installed), second returns True (now installed)
            mock_available.side_effect = [False, True]
            mock_cmd.return_value = (["brew", "install", "helm"], "Installing...")
            mock_run.return_value = Mock(stdout="v3.14.0", returncode=0)

            result = install_helm()

        assert result is True

    def test_helm_install_no_command(self):
        """Should fail when no install command available."""
        with patch("vr_cli.commands.setup.is_helm_available") as mock_available, \
             patch("vr_cli.commands.setup.get_helm_install_command") as mock_cmd, \
             patch("vr_cli.commands.setup.print_error"):
            mock_available.return_value = False
            mock_cmd.return_value = (None, "No package manager found")

            result = install_helm()

        assert result is False

    def test_helm_install_command_fails(self):
        """Should handle install command failure."""
        import subprocess

        with patch("vr_cli.commands.setup.is_helm_available") as mock_available, \
             patch("vr_cli.commands.setup.get_helm_install_command") as mock_cmd, \
             patch("subprocess.run") as mock_run, \
             patch("vr_cli.commands.setup.print_info"), \
             patch("vr_cli.commands.setup.print_error"):
            mock_available.return_value = False
            mock_cmd.return_value = (["brew", "install", "helm"], "Installing...")
            mock_run.side_effect = subprocess.CalledProcessError(1, "brew")

            result = install_helm()

        assert result is False

    def test_helm_install_but_not_in_path(self):
        """Should handle case where install succeeds but helm not in PATH."""
        with patch("vr_cli.commands.setup.is_helm_available") as mock_available, \
             patch("vr_cli.commands.setup.get_helm_install_command") as mock_cmd, \
             patch("subprocess.run") as mock_run, \
             patch("vr_cli.commands.setup.print_info"), \
             patch("vr_cli.commands.setup.print_error"):
            # Always returns False even after install
            mock_available.return_value = False
            mock_cmd.return_value = (["brew", "install", "helm"], "Installing...")
            mock_run.return_value = Mock(returncode=0)

            result = install_helm()

        assert result is False


class TestInstallSkills:
    """Tests for install_skills function."""

    def _make_skill_src(self, tmp_path, content="# Test skill"):
        """Helper to create a skill source directory with SKILL.md."""
        skills_src = tmp_path / "skills"
        skills_src.mkdir()
        skill_dir = skills_src / "vr-create-agent"
        skill_dir.mkdir()
        (skill_dir / "SKILL.md").write_text(content)
        return skills_src

    def test_copies_skill_to_single_target(self, tmp_path):
        """Should copy skill directory to the target path."""
        skills_src = self._make_skill_src(tmp_path)
        dest = tmp_path / "dest" / "skills"

        with patch("vr_cli.commands.setup.Path") as MockPath, \
             patch("vr_cli.commands.setup.TARGET_PATHS", {"claude-code": dest}), \
             patch("vr_cli.commands.setup.print_success"):
            mock_file = Mock()
            mock_file.parent.parent.__truediv__ = Mock(return_value=skills_src)
            MockPath.return_value = mock_file

            result = install_skills(["claude-code"])

        assert result is True
        assert (dest / "vr-create-agent" / "SKILL.md").exists()
        assert (dest / "vr-create-agent" / "SKILL.md").read_text() == "# Test skill"

    def test_copies_skill_to_multiple_targets(self, tmp_path):
        """Should copy skill directory to all specified targets."""
        skills_src = self._make_skill_src(tmp_path)
        dest_claude = tmp_path / "claude" / "skills"
        dest_codex = tmp_path / "codex" / "skills"
        dest_openclaw = tmp_path / "openclaw" / "skills"

        with patch("vr_cli.commands.setup.Path") as MockPath, \
             patch("vr_cli.commands.setup.TARGET_PATHS", {
                 "claude-code": dest_claude,
                 "codex": dest_codex,
                 "openclaw": dest_openclaw,
             }), \
             patch("vr_cli.commands.setup.print_success"):
            mock_file = Mock()
            mock_file.parent.parent.__truediv__ = Mock(return_value=skills_src)
            MockPath.return_value = mock_file

            result = install_skills(["claude-code", "codex", "openclaw"])

        assert result is True
        for dest in [dest_claude, dest_codex, dest_openclaw]:
            assert (dest / "vr-create-agent" / "SKILL.md").exists()

    def test_overwrites_existing_skill(self, tmp_path):
        """Should overwrite already-installed skill directories."""
        skills_src = self._make_skill_src(tmp_path, content="# Updated skill")
        dest = tmp_path / "dest" / "skills"

        # Pre-create destination with old content
        old_skill = dest / "vr-create-agent"
        old_skill.mkdir(parents=True)
        (old_skill / "SKILL.md").write_text("# Old skill")

        with patch("vr_cli.commands.setup.Path") as MockPath, \
             patch("vr_cli.commands.setup.TARGET_PATHS", {"claude-code": dest}), \
             patch("vr_cli.commands.setup.print_success"):
            mock_file = Mock()
            mock_file.parent.parent.__truediv__ = Mock(return_value=skills_src)
            MockPath.return_value = mock_file

            result = install_skills(["claude-code"])

        assert result is True
        assert (dest / "vr-create-agent" / "SKILL.md").read_text() == "# Updated skill"

    def test_removes_stale_files_on_reinstall(self, tmp_path):
        """Should remove files from previous version that no longer exist in source."""
        skills_src = self._make_skill_src(tmp_path, content="# Updated skill")
        dest = tmp_path / "dest" / "skills"

        # Pre-create destination with old content including a stale file
        old_skill = dest / "vr-create-agent"
        old_skill.mkdir(parents=True)
        (old_skill / "SKILL.md").write_text("# Old skill")
        (old_skill / "old_file.txt").write_text("stale content")

        with patch("vr_cli.commands.setup.Path") as MockPath, \
             patch("vr_cli.commands.setup.TARGET_PATHS", {"claude-code": dest}), \
             patch("vr_cli.commands.setup.print_success"):
            mock_file = Mock()
            mock_file.parent.parent.__truediv__ = Mock(return_value=skills_src)
            MockPath.return_value = mock_file

            result = install_skills(["claude-code"])

        assert result is True
        assert (dest / "vr-create-agent" / "SKILL.md").read_text() == "# Updated skill"
        assert not (dest / "vr-create-agent" / "old_file.txt").exists()

    def test_returns_false_when_skills_dir_missing(self, tmp_path):
        """Should return False when skills source directory doesn't exist."""
        missing_dir = tmp_path / "nonexistent"

        with patch("vr_cli.commands.setup.Path") as MockPath, \
             patch("vr_cli.commands.setup.print_warning"):
            mock_file = Mock()
            mock_file.parent.parent.__truediv__ = Mock(return_value=missing_dir)
            MockPath.return_value = mock_file

            result = install_skills(["claude-code"])

        assert result is False

    def test_returns_false_when_no_skill_dirs(self, tmp_path):
        """Should return False when no skill directories contain SKILL.md."""
        skills_src = tmp_path / "skills"
        skills_src.mkdir()
        # Create a file, not a valid skill directory
        (skills_src / "readme.txt").write_text("not a skill")

        with patch("vr_cli.commands.setup.Path") as MockPath, \
             patch("vr_cli.commands.setup.print_warning"):
            mock_file = Mock()
            mock_file.parent.parent.__truediv__ = Mock(return_value=skills_src)
            MockPath.return_value = mock_file

            result = install_skills(["claude-code"])

        assert result is False

    def test_returns_false_for_unknown_targets(self, tmp_path):
        """Should return False when all targets are unknown."""
        skills_src = self._make_skill_src(tmp_path)

        with patch("vr_cli.commands.setup.Path") as MockPath, \
             patch("vr_cli.commands.setup.print_warning"):
            mock_file = Mock()
            mock_file.parent.parent.__truediv__ = Mock(return_value=skills_src)
            MockPath.return_value = mock_file

            result = install_skills(["nonexistent-target"])

        assert result is False


def _setup_mocks():
    """Common mock setup for tests that invoke the full setup command."""
    return {
        "vr_cli.commands.setup.install_helm": Mock(return_value=True),
        "vr_cli.commands.setup.install_skills": Mock(return_value=True),
        "vr_cli.commands.setup.install_uv": Mock(return_value=True),
        "sys.stdin": Mock(isatty=Mock(return_value=False)),
    }


class TestSetupCommand:
    """Tests for the setup command."""

    def test_setup_installs_helm(self):
        """Should install Helm during setup."""
        with patch("vr_cli.commands.setup.install_helm") as mock_install, \
             patch("vr_cli.commands.setup.install_skills") as mock_skills, \
             patch("vr_cli.commands.setup.console"), \
             patch("vr_cli.commands.setup.sys") as mock_sys:
            mock_install.return_value = True
            mock_skills.return_value = True
            mock_sys.stdin.isatty.return_value = False

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

        mock_install.assert_called_once()
        assert result.exit_code == 0

    def test_setup_skip_helm(self):
        """Should skip Helm with --skip-helm flag."""
        with patch("vr_cli.commands.setup.install_helm") as mock_install, \
             patch("vr_cli.commands.setup.install_skills") as mock_skills, \
             patch("vr_cli.commands.setup.console"), \
             patch("vr_cli.commands.setup.print_info"), \
             patch("vr_cli.commands.setup.sys") as mock_sys:
            mock_skills.return_value = True
            mock_sys.stdin.isatty.return_value = False

            result = runner.invoke(app, ["setup", "--skip-helm"])

        mock_install.assert_not_called()
        assert result.exit_code == 0

    def test_setup_handles_helm_failure(self):
        """Should continue with warning if Helm install fails."""
        with patch("vr_cli.commands.setup.install_helm") as mock_install, \
             patch("vr_cli.commands.setup.install_skills") as mock_skills, \
             patch("vr_cli.commands.setup.console"), \
             patch("vr_cli.commands.setup.print_warning"), \
             patch("vr_cli.commands.setup.sys") as mock_sys:
            mock_install.return_value = False
            mock_skills.return_value = True
            mock_sys.stdin.isatty.return_value = False

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

        assert result.exit_code == 0  # Still succeeds with warning


class TestSetupCommandOutput:
    """Tests for setup command output."""

    def test_setup_shows_next_steps(self):
        """Should show next steps after setup."""
        with patch("vr_cli.commands.setup.install_helm") as mock_install, \
             patch("vr_cli.commands.setup.install_skills") as mock_skills, \
             patch("vr_cli.commands.setup.console") as mock_console, \
             patch("vr_cli.commands.setup.sys") as mock_sys:
            mock_install.return_value = True
            mock_skills.return_value = True
            mock_sys.stdin.isatty.return_value = False

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

        # Check that next steps were printed
        output_calls = [str(call) for call in mock_console.print.call_args_list]
        output_text = " ".join(output_calls)
        assert "vr signin" in output_text or "signin" in output_text.lower()


class TestSetupSkillsIntegration:
    """Tests for skills installation in the setup command."""

    def test_setup_skip_skills(self):
        """Should skip skills with --skip-skills flag."""
        with patch("vr_cli.commands.setup.install_skills") as mock_skills, \
             patch("vr_cli.commands.setup.install_helm") as mock_helm, \
             patch("vr_cli.commands.setup.console"), \
             patch("vr_cli.commands.setup.print_info"), \
             patch("vr_cli.commands.setup.sys") as mock_sys:
            mock_helm.return_value = True
            mock_sys.stdin.isatty.return_value = False

            result = runner.invoke(app, ["setup", "--skip-skills"])

        mock_skills.assert_not_called()
        assert result.exit_code == 0

    def test_setup_claude_code_flag(self):
        """Should install skills to claude-code only with --claude-code flag."""
        with patch("vr_cli.commands.setup.install_skills") as mock_skills, \
             patch("vr_cli.commands.setup.install_helm") as mock_helm, \
             patch("vr_cli.commands.setup.console"), \
             patch("vr_cli.commands.setup.sys") as mock_sys:
            mock_helm.return_value = True
            mock_skills.return_value = True
            mock_sys.stdin.isatty.return_value = False

            result = runner.invoke(app, ["setup", "--claude-code"])

        mock_skills.assert_called_once_with(["claude-code"])
        assert result.exit_code == 0

    def test_setup_codex_flag(self):
        """Should install skills to codex only with --codex flag."""
        with patch("vr_cli.commands.setup.install_skills") as mock_skills, \
             patch("vr_cli.commands.setup.install_helm") as mock_helm, \
             patch("vr_cli.commands.setup.console"), \
             patch("vr_cli.commands.setup.sys") as mock_sys:
            mock_helm.return_value = True
            mock_skills.return_value = True
            mock_sys.stdin.isatty.return_value = False

            result = runner.invoke(app, ["setup", "--codex"])

        mock_skills.assert_called_once_with(["codex"])
        assert result.exit_code == 0

    def test_setup_openclaw_flag(self):
        """Should install skills to openclaw only with --openclaw flag."""
        with patch("vr_cli.commands.setup.install_skills") as mock_skills, \
             patch("vr_cli.commands.setup.install_helm") as mock_helm, \
             patch("vr_cli.commands.setup.console"), \
             patch("vr_cli.commands.setup.sys") as mock_sys:
            mock_helm.return_value = True
            mock_skills.return_value = True
            mock_sys.stdin.isatty.return_value = False

            result = runner.invoke(app, ["setup", "--openclaw"])

        mock_skills.assert_called_once_with(["openclaw"])
        assert result.exit_code == 0

    def test_setup_combined_flags(self):
        """Should install skills to all specified targets when multiple flags set."""
        with patch("vr_cli.commands.setup.install_skills") as mock_skills, \
             patch("vr_cli.commands.setup.install_helm") as mock_helm, \
             patch("vr_cli.commands.setup.console"), \
             patch("vr_cli.commands.setup.sys") as mock_sys:
            mock_helm.return_value = True
            mock_skills.return_value = True
            mock_sys.stdin.isatty.return_value = False

            result = runner.invoke(app, ["setup", "--claude-code", "--codex", "--openclaw"])

        mock_skills.assert_called_once_with(["claude-code", "codex", "openclaw"])
        assert result.exit_code == 0

    def test_setup_interactive_prompt(self):
        """Should show interactive checkboxes when no flags and TTY."""
        with patch("vr_cli.commands.setup.install_skills") as mock_skills, \
             patch("vr_cli.commands.setup.install_helm") as mock_helm, \
             patch("vr_cli.commands.setup.install_mcp_server") as mock_mcp, \
             patch("vr_cli.commands.setup.console"), \
             patch("vr_cli.commands.setup.sys") as mock_sys, \
             patch("vr_cli.commands.setup.questionary") as mock_q:
            mock_helm.return_value = True
            mock_skills.return_value = True
            mock_mcp.return_value = True
            mock_sys.stdin.isatty.return_value = True
            mock_q.checkbox.return_value.ask.return_value = ["claude-code", "codex"]

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

        # checkbox is called twice: once for skills, once for MCP
        assert mock_q.checkbox.call_count == 2
        mock_skills.assert_called_once_with(["claude-code", "codex"])
        assert result.exit_code == 0

    def test_setup_interactive_no_selection(self):
        """Should skip skills when user selects nothing in interactive prompt."""
        with patch("vr_cli.commands.setup.install_skills") as mock_skills, \
             patch("vr_cli.commands.setup.install_helm") as mock_helm, \
             patch("vr_cli.commands.setup.console"), \
             patch("vr_cli.commands.setup.print_info"), \
             patch("vr_cli.commands.setup.sys") as mock_sys, \
             patch("vr_cli.commands.setup.questionary") as mock_q:
            mock_helm.return_value = True
            mock_sys.stdin.isatty.return_value = True
            mock_q.checkbox.return_value.ask.return_value = []

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

        mock_skills.assert_not_called()
        assert result.exit_code == 0

    def test_setup_interactive_cancel(self):
        """Should skip skills when user cancels interactive prompt (Ctrl+C)."""
        with patch("vr_cli.commands.setup.install_skills") as mock_skills, \
             patch("vr_cli.commands.setup.install_helm") as mock_helm, \
             patch("vr_cli.commands.setup.console"), \
             patch("vr_cli.commands.setup.print_info"), \
             patch("vr_cli.commands.setup.sys") as mock_sys, \
             patch("vr_cli.commands.setup.questionary") as mock_q:
            mock_helm.return_value = True
            mock_sys.stdin.isatty.return_value = True
            # questionary returns None when cancelled
            mock_q.checkbox.return_value.ask.return_value = None

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

        mock_skills.assert_not_called()
        assert result.exit_code == 0

    def test_setup_non_interactive_defaults_to_claude_code(self):
        """Should default to claude-code target in non-interactive mode."""
        with patch("vr_cli.commands.setup.install_skills") as mock_skills, \
             patch("vr_cli.commands.setup.install_helm") as mock_helm, \
             patch("vr_cli.commands.setup.console"), \
             patch("vr_cli.commands.setup.sys") as mock_sys:
            mock_helm.return_value = True
            mock_skills.return_value = True
            mock_sys.stdin.isatty.return_value = False

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

        mock_skills.assert_called_once_with(["claude-code"])
        assert result.exit_code == 0

    def test_setup_continues_when_skills_fail(self):
        """Should continue with warning if skills installation fails."""
        with patch("vr_cli.commands.setup.install_skills") as mock_skills, \
             patch("vr_cli.commands.setup.install_helm") as mock_helm, \
             patch("vr_cli.commands.setup.console"), \
             patch("vr_cli.commands.setup.print_warning"), \
             patch("vr_cli.commands.setup.sys") as mock_sys:
            mock_helm.return_value = True
            mock_skills.return_value = False
            mock_sys.stdin.isatty.return_value = False

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

        assert result.exit_code == 0


class TestSetupPersistsTargets:
    """Tests that setup persists selected targets for vr --update."""

    def test_saves_skill_and_mcp_targets(self, mock_save_setup_targets):
        """Should save both skill and MCP targets after setup."""
        with patch("vr_cli.commands.setup.install_skills") as mock_skills, \
             patch("vr_cli.commands.setup.install_helm") as mock_helm, \
             patch("vr_cli.commands.setup.install_mcp_server") as mock_mcp, \
             patch("vr_cli.commands.setup.console"), \
             patch("vr_cli.commands.setup.sys") as mock_sys:
            mock_helm.return_value = True
            mock_skills.return_value = True
            mock_mcp.return_value = True
            mock_sys.stdin.isatty.return_value = False

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

        assert result.exit_code == 0
        mock_save_setup_targets.assert_called_once_with(["claude-code"], ["claude-code"])

    def test_saves_selected_flag_targets(self, mock_save_setup_targets):
        """Should save the specific targets chosen via flags."""
        with patch("vr_cli.commands.setup.install_skills") as mock_skills, \
             patch("vr_cli.commands.setup.install_helm") as mock_helm, \
             patch("vr_cli.commands.setup.install_mcp_server") as mock_mcp, \
             patch("vr_cli.commands.setup.console"), \
             patch("vr_cli.commands.setup.sys") as mock_sys:
            mock_helm.return_value = True
            mock_skills.return_value = True
            mock_mcp.return_value = True
            mock_sys.stdin.isatty.return_value = False

            result = runner.invoke(app, ["setup", "--claude-code", "--codex", "--cursor"])

        assert result.exit_code == 0
        mock_save_setup_targets.assert_called_once_with(
            ["claude-code", "codex"],
            ["claude-code", "cursor", "codex"],
        )

    def test_does_not_save_when_all_skipped(self, mock_save_setup_targets):
        """Should not save targets when both skills and MCP are skipped."""
        with patch("vr_cli.commands.setup.install_helm") as mock_helm, \
             patch("vr_cli.commands.setup.console"), \
             patch("vr_cli.commands.setup.print_info"), \
             patch("vr_cli.commands.setup.sys") as mock_sys:
            mock_helm.return_value = True
            mock_sys.stdin.isatty.return_value = False

            result = runner.invoke(app, ["setup", "--skip-skills", "--skip-mcp"])

        assert result.exit_code == 0
        mock_save_setup_targets.assert_not_called()

    def test_saves_interactive_selections(self, mock_save_setup_targets):
        """Should save targets chosen via interactive prompts."""
        with patch("vr_cli.commands.setup.install_skills") as mock_skills, \
             patch("vr_cli.commands.setup.install_helm") as mock_helm, \
             patch("vr_cli.commands.setup.install_mcp_server") as mock_mcp, \
             patch("vr_cli.commands.setup.console"), \
             patch("vr_cli.commands.setup.sys") as mock_sys, \
             patch("vr_cli.commands.setup.questionary") as mock_q:
            mock_helm.return_value = True
            mock_skills.return_value = True
            mock_mcp.return_value = True
            mock_sys.stdin.isatty.return_value = True
            # Simulate interactive checkbox selections
            mock_q.checkbox.return_value.ask.side_effect = [
                ["claude-code", "openclaw"],  # skills prompt
                ["cursor", "windsurf"],  # MCP prompt
            ]

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

        assert result.exit_code == 0
        mock_save_setup_targets.assert_called_once_with(
            ["claude-code", "openclaw"],
            ["cursor", "windsurf"],
        )
