"""Comprehensive unit tests for the setup command.

This module provides thorough testing of the setup command functionality including:
- File extension scanning with timeout protection
- Setup workflow orchestration
- Language detection
- MCP platform configuration
- Error handling and edge cases

NOTE: Many tests in this module are skipped as they reference the old
detect_installed_platforms function which has been replaced with detect_all_platforms
from py_mcp_installer. These tests need to be updated to use the new PlatformInfo
data structure.
"""

import json
import time
from pathlib import Path
from unittest.mock import Mock, patch

import pytest

from mcp_vector_search.cli.commands.setup import (
    _run_smart_setup,
    scan_project_file_extensions,
    select_optimal_embedding_model,
)
from mcp_vector_search.core.project import ProjectManager

# ==============================================================================
# Test Fixtures
# ==============================================================================


@pytest.fixture
def mock_python_project(tmp_path):
    """Create a mock Python project."""
    (tmp_path / "main.py").write_text("print('hello')")
    (tmp_path / "utils.py").write_text("def foo(): pass")
    (tmp_path / "README.md").write_text("# Project")
    (tmp_path / ".git").mkdir()  # Git repo marker
    return tmp_path


@pytest.fixture
def mock_multi_language_project(tmp_path):
    """Create project with Python and JavaScript."""
    (tmp_path / "app.py").write_text("print('python')")
    (tmp_path / "index.js").write_text("console.log('js')")
    (tmp_path / "styles.css").write_text("body { margin: 0; }")
    (tmp_path / ".git").mkdir()
    return tmp_path


@pytest.fixture
def mock_large_project(tmp_path):
    """Create project with many files (test timeout)."""
    for i in range(100):
        (tmp_path / f"file_{i}.py").write_text("pass")
    (tmp_path / ".git").mkdir()
    return tmp_path


@pytest.fixture
def mock_empty_project(tmp_path):
    """Create empty project directory."""
    (tmp_path / ".git").mkdir()
    return tmp_path


@pytest.fixture
def mock_typer_context(tmp_path):
    """Create a mock Typer context."""
    context = Mock()
    context.obj = {"project_root": tmp_path}
    context.invoked_subcommand = None
    return context


# ==============================================================================
# A. File Extension Scanner Tests
# ==============================================================================


class TestScanProjectFileExtensions:
    """Test suite for scan_project_file_extensions function."""

    def test_scan_project_file_extensions_basic(self, mock_python_project):
        """Test scanning basic Python project finds .py and .md extensions."""
        # Act
        extensions = scan_project_file_extensions(mock_python_project, timeout=2.0)

        # Assert
        assert extensions is not None
        assert ".py" in extensions
        assert ".md" in extensions
        assert extensions == sorted(extensions)  # Should be sorted

    def test_scan_project_file_extensions_timeout(self, tmp_path):
        """Test that scan respects timeout and returns None."""
        # Arrange - Create a project structure that will take time to scan
        # We'll mock the time to force timeout
        for i in range(10):
            (tmp_path / f"file_{i}.py").write_text("pass")

        # Act - Use a very short timeout and mock time to force timeout
        with patch("time.time") as mock_time:
            # First call returns start time, subsequent calls force timeout
            mock_time.side_effect = [0.0, 3.0]  # Timeout after 2s
            extensions = scan_project_file_extensions(tmp_path, timeout=2.0)

        # Assert
        assert extensions is None

    def test_scan_project_file_extensions_respects_gitignore(self, tmp_path):
        """Test that scan respects .gitignore patterns."""
        # Arrange
        (tmp_path / ".git").mkdir()
        (tmp_path / "main.py").write_text("print('hello')")
        (tmp_path / "node_modules").mkdir()
        (tmp_path / "node_modules" / "lib.js").write_text("module.exports = {}")
        (tmp_path / ".gitignore").write_text("node_modules/\n")

        # Act
        extensions = scan_project_file_extensions(tmp_path, timeout=2.0)

        # Assert
        assert extensions is not None
        assert ".py" in extensions
        assert ".js" not in extensions  # Should be ignored

    def test_scan_project_file_extensions_empty_project(self, mock_empty_project):
        """Test scanning empty project returns None."""
        # Act
        extensions = scan_project_file_extensions(mock_empty_project, timeout=2.0)

        # Assert
        assert extensions is None

    def test_scan_project_file_extensions_unknown_extensions(self, tmp_path):
        """Test that unknown/unsupported extensions are filtered out."""
        # Arrange
        (tmp_path / "main.py").write_text("print('hello')")
        (tmp_path / "data.xyz").write_text("unknown format")
        (tmp_path / "binary.bin").write_text("binary data")

        # Act
        extensions = scan_project_file_extensions(tmp_path, timeout=2.0)

        # Assert
        assert extensions is not None
        assert ".py" in extensions
        # Unknown extensions should be filtered unless they're common text formats
        # The function filters based on get_language_from_extension

    def test_scan_project_file_extensions_handles_exceptions(self, tmp_path):
        """Test that scan handles exceptions gracefully."""
        # Arrange - Mock _should_ignore_path to raise exception
        with patch.object(
            ProjectManager, "_should_ignore_path", side_effect=Exception("Test error")
        ):
            # Act
            extensions = scan_project_file_extensions(tmp_path, timeout=2.0)

            # Assert
            assert extensions is None

    def test_scan_project_file_extensions_multi_language(
        self, mock_multi_language_project
    ):
        """Test scanning multi-language project finds all extensions."""
        # Act
        extensions = scan_project_file_extensions(
            mock_multi_language_project, timeout=2.0
        )

        # Assert
        assert extensions is not None
        assert ".py" in extensions
        assert ".js" in extensions
        # Note: .css is filtered out as it's not a recognized code extension


# ==============================================================================
# B. Embedding Model Selection Tests
# ==============================================================================


class TestSelectOptimalEmbeddingModel:
    """Test suite for select_optimal_embedding_model function."""

    def test_select_code_model_for_python(self):
        """Test that Python projects get code-optimized model."""
        # Act
        model = select_optimal_embedding_model(["Python"])

        # Assert
        assert "code" in model.lower() or "MiniLM" in model

    def test_select_code_model_for_javascript(self):
        """Test that JavaScript projects get code-optimized model."""
        # Act
        model = select_optimal_embedding_model(["JavaScript"])

        # Assert
        assert "code" in model.lower() or "MiniLM" in model

    def test_select_code_model_for_multi_language(self):
        """Test multi-language projects get code model."""
        # Act
        model = select_optimal_embedding_model(["Python", "JavaScript", "Go"])

        # Assert
        assert "code" in model.lower() or "MiniLM" in model

    def test_select_default_for_no_languages(self):
        """Test that empty language list returns default model."""
        # Act
        model = select_optimal_embedding_model([])

        # Assert
        assert model is not None

    def test_select_default_for_non_code_languages(self):
        """Test that non-code languages still get code model (current behavior)."""
        # Act
        model = select_optimal_embedding_model(["Markdown", "Text"])

        # Assert
        assert model is not None


# ==============================================================================
# C. Setup Command Tests
# ==============================================================================


@pytest.mark.skip(
    reason="Tests need update for py_mcp_installer PlatformInfo data structure"
)
class TestSetupCommand:
    """Test suite for main setup command functionality."""

    @pytest.mark.asyncio
    async def test_setup_fresh_project(self, mock_python_project, mock_typer_context):
        """Test complete setup in fresh project."""
        # Arrange
        mock_typer_context.obj = {"project_root": mock_python_project}

        with (
            patch(
                "mcp_vector_search.cli.commands.setup.detect_installed_platforms"
            ) as mock_detect,
            patch(
                "mcp_vector_search.cli.commands.setup.check_claude_cli_available"
            ) as mock_check_claude,
            patch(
                "mcp_vector_search.cli.commands.setup.check_uv_available"
            ) as mock_check_uv,
            patch("subprocess.run") as mock_subprocess,
            patch("mcp_vector_search.cli.commands.index.run_indexing") as mock_index,
        ):
            mock_detect.return_value = {"claude-code": Path(".mcp.json")}
            mock_check_claude.return_value = True
            mock_check_uv.return_value = True
            mock_subprocess.return_value = type(
                "obj", (object,), {"returncode": 0, "stdout": "", "stderr": ""}
            )()
            mock_index.return_value = None

            # Act
            await _run_smart_setup(mock_typer_context, force=False, verbose=False)

            # Assert
            # Check that project was initialized
            project_manager = ProjectManager(mock_python_project)
            assert project_manager.is_initialized()

            # Check indexing was called
            mock_index.assert_called_once()

            # Check platform configuration was called via subprocess
            assert mock_subprocess.call_count >= 2

    @pytest.mark.asyncio
    async def test_setup_already_initialized(
        self, mock_python_project, mock_typer_context
    ):
        """Test setup behavior when project already initialized."""
        # Arrange
        mock_typer_context.obj = {"project_root": mock_python_project}

        # Initialize project first
        project_manager = ProjectManager(mock_python_project)
        project_manager.initialize(
            file_extensions=[".py"], embedding_model="test-model"
        )

        with (
            patch(
                "mcp_vector_search.cli.commands.setup.detect_installed_platforms"
            ) as mock_detect,
            patch(
                "mcp_vector_search.cli.commands.setup.configure_platform"
            ) as mock_configure,
            patch("mcp_vector_search.cli.commands.index.run_indexing") as mock_index,
        ):
            mock_detect.return_value = {}
            mock_configure.return_value = True

            # Act
            await _run_smart_setup(mock_typer_context, force=False, verbose=False)

            # Assert
            # Indexing should be skipped when already initialized
            mock_index.assert_not_called()

    @pytest.mark.asyncio
    async def test_setup_with_force_flag(self, mock_python_project, mock_typer_context):
        """Test setup with force flag re-initializes."""
        # Arrange
        mock_typer_context.obj = {"project_root": mock_python_project}

        # Initialize project first
        project_manager = ProjectManager(mock_python_project)
        project_manager.initialize(
            file_extensions=[".py"], embedding_model="test-model"
        )

        with (
            patch(
                "mcp_vector_search.cli.commands.setup.detect_installed_platforms"
            ) as mock_detect,
            patch(
                "mcp_vector_search.cli.commands.setup.configure_platform"
            ) as mock_configure,
            patch("mcp_vector_search.cli.commands.index.run_indexing") as mock_index,
        ):
            mock_detect.return_value = {}
            mock_configure.return_value = True
            mock_index.return_value = None

            # Act
            await _run_smart_setup(mock_typer_context, force=True, verbose=False)

            # Assert
            # With force=True, indexing should happen even if already initialized
            mock_index.assert_called_once()

    @pytest.mark.asyncio
    async def test_setup_detects_languages(
        self, mock_multi_language_project, mock_typer_context
    ):
        """Test that setup detects project languages."""
        # Arrange
        mock_typer_context.obj = {"project_root": mock_multi_language_project}

        with (
            patch(
                "mcp_vector_search.cli.commands.setup.detect_installed_platforms"
            ) as mock_detect,
            patch(
                "mcp_vector_search.cli.commands.setup.configure_platform"
            ) as mock_configure,
            patch("mcp_vector_search.cli.commands.index.run_indexing") as mock_index,
        ):
            mock_detect.return_value = {}
            mock_configure.return_value = True
            mock_index.return_value = None

            # Act
            await _run_smart_setup(mock_typer_context, force=False, verbose=True)

            # Assert
            project_manager = ProjectManager(mock_multi_language_project)
            languages = project_manager.detect_languages()
            assert len(languages) > 0

    @pytest.mark.asyncio
    async def test_setup_detects_mcp_platforms(
        self, mock_python_project, mock_typer_context
    ):
        """Test that setup detects installed MCP platforms."""
        # Arrange
        mock_typer_context.obj = {"project_root": mock_python_project}

        with (
            patch(
                "mcp_vector_search.cli.commands.setup.detect_installed_platforms"
            ) as mock_detect,
            patch(
                "mcp_vector_search.cli.commands.setup.check_claude_cli_available"
            ) as mock_check_claude,
            patch(
                "mcp_vector_search.cli.commands.setup.check_uv_available"
            ) as mock_check_uv,
            patch("subprocess.run") as mock_subprocess,
            patch(
                "mcp_vector_search.cli.commands.setup.configure_platform"
            ) as mock_configure,
            patch("mcp_vector_search.cli.commands.index.run_indexing") as mock_index,
        ):
            mock_detect.return_value = {
                "claude-code": Path(".mcp.json"),
                "cursor": Path("~/.cursor/mcp.json"),
            }
            mock_check_claude.return_value = True
            mock_check_uv.return_value = True
            mock_subprocess.return_value = type(
                "obj", (object,), {"returncode": 0, "stdout": "", "stderr": ""}
            )()
            mock_configure.return_value = True
            mock_index.return_value = None

            # Act
            await _run_smart_setup(mock_typer_context, force=False, verbose=False)

            # Assert
            # Should configure claude-code via subprocess and cursor via configure_platform
            assert mock_subprocess.call_count >= 2
            assert mock_configure.call_count == 1

    @pytest.mark.asyncio
    async def test_setup_creates_gitignore_entry(
        self, mock_python_project, mock_typer_context
    ):
        """Test that setup creates .gitignore entry."""
        # Arrange
        mock_typer_context.obj = {"project_root": mock_python_project}
        gitignore_path = mock_python_project / ".gitignore"
        gitignore_path.write_text("*.pyc\n")

        with (
            patch(
                "mcp_vector_search.cli.commands.setup.detect_installed_platforms"
            ) as mock_detect,
            patch(
                "mcp_vector_search.cli.commands.setup.configure_platform"
            ) as mock_configure,
            patch("mcp_vector_search.cli.commands.index.run_indexing") as mock_index,
        ):
            mock_detect.return_value = {}
            mock_configure.return_value = True
            mock_index.return_value = None

            # Act
            await _run_smart_setup(mock_typer_context, force=False, verbose=False)

            # Assert
            gitignore_content = gitignore_path.read_text()
            assert ".mcp-vector-search/" in gitignore_content

    @pytest.mark.asyncio
    async def test_setup_creates_mcp_json(
        self, mock_python_project, mock_typer_context
    ):
        """Test that setup creates .mcp.json for Claude Code."""
        # Arrange
        mock_typer_context.obj = {"project_root": mock_python_project}

        with (
            patch(
                "mcp_vector_search.cli.commands.setup.detect_installed_platforms"
            ) as mock_detect,
            patch(
                "mcp_vector_search.cli.commands.setup.check_claude_cli_available"
            ) as mock_check_claude,
            patch(
                "mcp_vector_search.cli.commands.setup.check_uv_available"
            ) as mock_check_uv,
            patch("subprocess.run") as mock_subprocess,
            patch("mcp_vector_search.cli.commands.index.run_indexing") as mock_index,
        ):
            mock_detect.return_value = {"claude-code": Path(".mcp.json")}
            mock_check_claude.return_value = True
            mock_check_uv.return_value = True
            mock_subprocess.return_value = type(
                "obj", (object,), {"returncode": 0, "stdout": "", "stderr": ""}
            )()
            mock_index.return_value = None

            # Act
            await _run_smart_setup(mock_typer_context, force=False, verbose=False)

            # Assert - Should have called subprocess for MCP registration
            assert mock_subprocess.call_count >= 2
            # Verify remove and add commands
            subprocess_calls = mock_subprocess.call_args_list
            remove_call = subprocess_calls[0][0][0]
            assert "remove" in remove_call
            add_call = subprocess_calls[1][0][0]
            assert "add" in add_call

    @pytest.mark.asyncio
    async def test_setup_indexes_codebase(
        self, mock_python_project, mock_typer_context
    ):
        """Test that setup indexes the codebase."""
        # Arrange
        mock_typer_context.obj = {"project_root": mock_python_project}

        with (
            patch(
                "mcp_vector_search.cli.commands.setup.detect_installed_platforms"
            ) as mock_detect,
            patch(
                "mcp_vector_search.cli.commands.setup.configure_platform"
            ) as mock_configure,
            patch("mcp_vector_search.cli.commands.index.run_indexing") as mock_index,
        ):
            mock_detect.return_value = {}
            mock_configure.return_value = True
            mock_index.return_value = None

            # Act
            await _run_smart_setup(mock_typer_context, force=False, verbose=False)

            # Assert
            mock_index.assert_called_once()
            call_kwargs = mock_index.call_args[1]
            assert call_kwargs["project_root"] == mock_python_project
            assert call_kwargs["show_progress"] is True

    @pytest.mark.asyncio
    async def test_setup_configures_all_platforms(
        self, mock_python_project, mock_typer_context
    ):
        """Test that setup configures all detected platforms."""
        # Arrange
        mock_typer_context.obj = {"project_root": mock_python_project}

        with (
            patch(
                "mcp_vector_search.cli.commands.setup.detect_installed_platforms"
            ) as mock_detect,
            patch(
                "mcp_vector_search.cli.commands.setup.check_claude_cli_available"
            ) as mock_check_claude,
            patch(
                "mcp_vector_search.cli.commands.setup.check_uv_available"
            ) as mock_check_uv,
            patch("subprocess.run") as mock_subprocess,
            patch(
                "mcp_vector_search.cli.commands.setup.configure_platform"
            ) as mock_configure,
            patch("mcp_vector_search.cli.commands.index.run_indexing") as mock_index,
        ):
            mock_detect.return_value = {
                "claude-code": Path(".mcp.json"),
                "cursor": Path("~/.cursor/mcp.json"),
                "windsurf": Path("~/.codeium/windsurf/mcp_config.json"),
            }
            mock_check_claude.return_value = True
            mock_check_uv.return_value = True
            mock_subprocess.return_value = type(
                "obj", (object,), {"returncode": 0, "stdout": "", "stderr": ""}
            )()
            mock_configure.return_value = True
            mock_index.return_value = None

            # Act
            await _run_smart_setup(mock_typer_context, force=False, verbose=False)

            # Assert
            # claude-code uses subprocess, other platforms use configure_platform
            assert mock_subprocess.call_count >= 2
            assert mock_configure.call_count == 2  # cursor and windsurf
            # Extract platform names from configure_platform calls
            platforms_configured = []
            for call in mock_configure.call_args_list:
                if call[0]:
                    platforms_configured.append(call[0][0])
                elif "platform" in call[1]:
                    platforms_configured.append(call[1]["platform"])

            assert "cursor" in platforms_configured
            assert "windsurf" in platforms_configured


# ==============================================================================
# D. Error Handling Tests
# ==============================================================================


@pytest.mark.skip(
    reason="Tests need update for py_mcp_installer PlatformInfo data structure"
)
class TestSetupErrorHandling:
    """Test suite for error handling in setup command."""

    @pytest.mark.asyncio
    async def test_setup_handles_permission_errors(
        self, mock_python_project, mock_typer_context
    ):
        """Test that setup handles permission errors gracefully."""
        # Arrange
        mock_typer_context.obj = {"project_root": mock_python_project}

        with (
            patch(
                "mcp_vector_search.cli.commands.setup.detect_installed_platforms"
            ) as mock_detect,
            patch(
                "mcp_vector_search.cli.commands.setup.configure_platform"
            ) as mock_configure,
            patch("mcp_vector_search.cli.commands.index.run_indexing") as mock_index,
        ):
            mock_detect.return_value = {}
            mock_configure.return_value = True
            # Simulate permission error during indexing
            mock_index.side_effect = PermissionError("Access denied")

            # Act & Assert - Should not raise, should continue
            await _run_smart_setup(mock_typer_context, force=False, verbose=False)

            # Setup should complete despite indexing failure
            project_manager = ProjectManager(mock_python_project)
            assert project_manager.is_initialized()

    @pytest.mark.asyncio
    async def test_setup_handles_non_git_repo(self, tmp_path, mock_typer_context):
        """Test that setup works in non-git repositories."""
        # Arrange - Don't create .git directory
        mock_typer_context.obj = {"project_root": tmp_path}
        (tmp_path / "main.py").write_text("print('hello')")

        with (
            patch(
                "mcp_vector_search.cli.commands.setup.detect_installed_platforms"
            ) as mock_detect,
            patch(
                "mcp_vector_search.cli.commands.setup.configure_platform"
            ) as mock_configure,
            patch("mcp_vector_search.cli.commands.index.run_indexing") as mock_index,
        ):
            mock_detect.return_value = {}
            mock_configure.return_value = True
            mock_index.return_value = None

            # Act
            await _run_smart_setup(mock_typer_context, force=False, verbose=False)

            # Assert
            project_manager = ProjectManager(tmp_path)
            assert project_manager.is_initialized()

    @pytest.mark.asyncio
    async def test_setup_handles_large_codebase(
        self, mock_large_project, mock_typer_context
    ):
        """Test that setup handles large codebases with timeout protection."""
        # Arrange
        mock_typer_context.obj = {"project_root": mock_large_project}

        with (
            patch(
                "mcp_vector_search.cli.commands.setup.detect_installed_platforms"
            ) as mock_detect,
            patch(
                "mcp_vector_search.cli.commands.setup.configure_platform"
            ) as mock_configure,
            patch("mcp_vector_search.cli.commands.index.run_indexing") as mock_index,
        ):
            mock_detect.return_value = {}
            mock_configure.return_value = True
            mock_index.return_value = None

            # Act - Should complete without hanging
            start_time = time.time()
            await _run_smart_setup(mock_typer_context, force=False, verbose=False)
            elapsed = time.time() - start_time

            # Assert - Should complete reasonably fast (file scan has timeout)
            assert elapsed < 30.0  # Generous timeout for test

    @pytest.mark.asyncio
    async def test_setup_continues_on_mcp_errors(
        self, mock_python_project, mock_typer_context
    ):
        """Test that setup continues if MCP platform configuration fails."""
        # Arrange
        mock_typer_context.obj = {"project_root": mock_python_project}

        with (
            patch(
                "mcp_vector_search.cli.commands.setup.detect_installed_platforms"
            ) as mock_detect,
            patch(
                "mcp_vector_search.cli.commands.setup.configure_platform"
            ) as mock_configure,
            patch("mcp_vector_search.cli.commands.index.run_indexing") as mock_index,
        ):
            mock_detect.return_value = {
                "claude-code": Path(".mcp.json"),
                "cursor": Path("~/.cursor/mcp.json"),
            }
            # First platform succeeds, second fails
            mock_configure.side_effect = [True, Exception("MCP config failed")]
            mock_index.return_value = None

            # Act - Should not raise exception
            await _run_smart_setup(mock_typer_context, force=False, verbose=False)

            # Assert - Project should still be initialized
            project_manager = ProjectManager(mock_python_project)
            assert project_manager.is_initialized()

    @pytest.mark.asyncio
    async def test_setup_handles_indexing_failure(
        self, mock_python_project, mock_typer_context
    ):
        """Test that setup handles indexing failures gracefully."""
        # Arrange
        mock_typer_context.obj = {"project_root": mock_python_project}

        with (
            patch(
                "mcp_vector_search.cli.commands.setup.detect_installed_platforms"
            ) as mock_detect,
            patch(
                "mcp_vector_search.cli.commands.setup.check_claude_cli_available"
            ) as mock_check_claude,
            patch(
                "mcp_vector_search.cli.commands.setup.check_uv_available"
            ) as mock_check_uv,
            patch("subprocess.run") as mock_subprocess,
            patch("mcp_vector_search.cli.commands.index.run_indexing") as mock_index,
        ):
            mock_detect.return_value = {"claude-code": Path(".mcp.json")}
            mock_check_claude.return_value = True
            mock_check_uv.return_value = True
            mock_subprocess.return_value = type(
                "obj", (object,), {"returncode": 0, "stdout": "", "stderr": ""}
            )()
            mock_index.side_effect = Exception("Indexing failed")

            # Act - Should complete setup despite indexing failure
            await _run_smart_setup(mock_typer_context, force=False, verbose=False)

            # Assert
            project_manager = ProjectManager(mock_python_project)
            assert project_manager.is_initialized()
            # MCP configuration should still happen
            assert mock_subprocess.call_count >= 2


# ==============================================================================
# E. Verbose Mode Tests
# ==============================================================================


@pytest.mark.skip(
    reason="Tests need update for py_mcp_installer PlatformInfo data structure"
)
class TestSetupVerboseMode:
    """Test suite for verbose output in setup command."""

    @pytest.mark.asyncio
    async def test_setup_verbose_mode_shows_details(
        self, mock_python_project, mock_typer_context, capsys
    ):
        """Test that verbose mode shows detailed information."""
        # Arrange
        mock_typer_context.obj = {"project_root": mock_python_project}

        with (
            patch(
                "mcp_vector_search.cli.commands.setup.detect_installed_platforms"
            ) as mock_detect,
            patch(
                "mcp_vector_search.cli.commands.setup.configure_platform"
            ) as mock_configure,
            patch("mcp_vector_search.cli.commands.index.run_indexing") as mock_index,
        ):
            mock_detect.return_value = {"claude-code": Path(".mcp.json")}
            mock_configure.return_value = True
            mock_index.return_value = None

            # Act
            await _run_smart_setup(mock_typer_context, force=False, verbose=True)

            # Assert - verbose output should be produced (captured via logging/console)
            # This is a smoke test to ensure verbose=True doesn't cause errors
            project_manager = ProjectManager(mock_python_project)
            assert project_manager.is_initialized()


# ==============================================================================
# F. Edge Case Tests
# ==============================================================================


@pytest.mark.skip(
    reason="Tests need update for py_mcp_installer PlatformInfo data structure"
)
class TestSetupEdgeCases:
    """Test suite for edge cases in setup command."""

    def test_scan_with_symlinks(self, tmp_path):
        """Test that scan handles symlinks correctly."""
        # Arrange
        (tmp_path / "main.py").write_text("print('hello')")
        (tmp_path / "lib").mkdir()
        (tmp_path / "lib" / "util.py").write_text("def foo(): pass")

        # Create symlink
        link_path = tmp_path / "link_to_lib"
        link_path.symlink_to(tmp_path / "lib", target_is_directory=True)

        # Act
        extensions = scan_project_file_extensions(tmp_path, timeout=2.0)

        # Assert
        assert extensions is not None
        assert ".py" in extensions

    def test_scan_with_binary_files(self, tmp_path):
        """Test that scan handles binary files correctly."""
        # Arrange
        (tmp_path / "main.py").write_text("print('hello')")
        (tmp_path / "image.png").write_bytes(b"\x89PNG\r\n\x1a\n")

        # Act
        extensions = scan_project_file_extensions(tmp_path, timeout=2.0)

        # Assert
        assert extensions is not None
        assert ".py" in extensions
        # .png should be filtered out as it's not a known text/code extension

    @pytest.mark.asyncio
    async def test_setup_with_no_detected_platforms(
        self, mock_python_project, mock_typer_context
    ):
        """Test setup when no MCP platforms are detected."""
        # Arrange
        mock_typer_context.obj = {"project_root": mock_python_project}

        with (
            patch(
                "mcp_vector_search.cli.commands.setup.detect_installed_platforms"
            ) as mock_detect,
            patch(
                "mcp_vector_search.cli.commands.setup.check_claude_cli_available"
            ) as mock_check_claude,
            patch(
                "mcp_vector_search.cli.commands.setup.check_uv_available"
            ) as mock_check_uv,
            patch("subprocess.run") as mock_subprocess,
            patch("mcp_vector_search.cli.commands.index.run_indexing") as mock_index,
        ):
            mock_detect.return_value = {}  # No platforms detected
            mock_check_claude.return_value = True
            mock_check_uv.return_value = True
            mock_subprocess.return_value = type(
                "obj", (object,), {"returncode": 0, "stdout": "", "stderr": ""}
            )()
            mock_index.return_value = None

            # Act
            await _run_smart_setup(mock_typer_context, force=False, verbose=False)

            # Assert - Should still configure claude-code as fallback via subprocess
            assert mock_subprocess.call_count >= 2

    def test_scan_respects_custom_timeout(self, mock_python_project):
        """Test that scan respects custom timeout values."""
        # Act with very short timeout
        extensions = scan_project_file_extensions(mock_python_project, timeout=0.001)

        # Assert - May timeout with such a short duration
        # Either returns results or None, but should not hang
        assert extensions is None or isinstance(extensions, list)

    @pytest.mark.asyncio
    async def test_setup_with_existing_mcp_json(
        self, mock_python_project, mock_typer_context
    ):
        """Test setup when .mcp.json already exists."""
        # Arrange
        mock_typer_context.obj = {"project_root": mock_python_project}
        mcp_json_path = mock_python_project / ".mcp.json"
        mcp_json_path.write_text(
            json.dumps({"mcpServers": {"other-server": {"command": "other"}}})
        )

        with (
            patch(
                "mcp_vector_search.cli.commands.setup.detect_installed_platforms"
            ) as mock_detect,
            patch(
                "mcp_vector_search.cli.commands.setup.check_claude_cli_available"
            ) as mock_check_claude,
            patch(
                "mcp_vector_search.cli.commands.setup.check_uv_available"
            ) as mock_check_uv,
            patch("subprocess.run") as mock_subprocess,
            patch("mcp_vector_search.cli.commands.index.run_indexing") as mock_index,
        ):
            mock_detect.return_value = {"claude-code": Path(".mcp.json")}
            mock_check_claude.return_value = True
            mock_check_uv.return_value = True
            mock_subprocess.return_value = type(
                "obj", (object,), {"returncode": 0, "stdout": "", "stderr": ""}
            )()
            mock_index.return_value = None

            # Act
            await _run_smart_setup(mock_typer_context, force=False, verbose=False)

            # Assert - Should have called subprocess for MCP registration
            # The actual file preservation is handled by claude CLI
            assert mock_subprocess.call_count >= 2
