
"""
Comprehensive tests for MCP server functionality.
Tests cover initialization, tool handling, resource management, and server lifecycle.
"""

import asyncio
import json
import os
import tempfile
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, Mock, patch
from typing import Any, Dict, List

import pytest

from tree_sitter_analyzer.mcp.server import (
    TreeSitterAnalyzerMCPServer,
    main,
    main_sync,
    parse_mcp_args,
)
from tree_sitter_analyzer.mcp.utils.error_handler import AnalysisError


class TestTreeSitterAnalyzerMCPServerInitialization:
    """Test MCP server initialization and setup."""

    def test_server_initialization_success(self, temp_project_dir):
        """Test successful server initialization."""
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        
        assert server.is_initialized()
        # Check project root through security validator
        actual_project_root = getattr(
            getattr(server.security_validator, "boundary_manager", None),
            "project_root",
            None,
        )
        assert actual_project_root == temp_project_dir
        assert server.name == "tree-sitter-analyzer-mcp"
        assert server.version is not None
        assert hasattr(server, 'analysis_engine')
        assert hasattr(server, 'security_validator')

    def test_server_initialization_with_tools(self, temp_project_dir):
        """Test server initialization includes all required tools."""
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        
        # Check core tools
        assert hasattr(server, 'query_tool')
        assert hasattr(server, 'read_partial_tool')
        assert hasattr(server, 'table_format_tool')
        assert hasattr(server, 'analyze_scale_tool')
        assert hasattr(server, 'list_files_tool')
        assert hasattr(server, 'search_content_tool')
        assert hasattr(server, 'find_and_grep_tool')

    def test_server_initialization_with_resources(self, temp_project_dir):
        """Test server initialization includes required resources."""
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        
        assert hasattr(server, 'code_file_resource')
        assert hasattr(server, 'project_stats_resource')

    def test_server_initialization_with_universal_tool(self, temp_project_dir):
        """Test server initialization with optional universal tool."""
        with patch('tree_sitter_analyzer.mcp.server.UniversalAnalyzeTool') as mock_tool:
            mock_instance = Mock()
            mock_tool.return_value = mock_instance
            
            server = TreeSitterAnalyzerMCPServer(temp_project_dir)
            
            assert hasattr(server, 'universal_analyze_tool')
            assert server.universal_analyze_tool == mock_instance

    def test_server_initialization_without_universal_tool(self, temp_project_dir):
        """Test server initialization when universal tool is not available."""
        with patch('tree_sitter_analyzer.mcp.server.UniversalAnalyzeTool', side_effect=ImportError):
            server = TreeSitterAnalyzerMCPServer(temp_project_dir)
            
            assert server.universal_analyze_tool is None

    def test_ensure_initialized_success(self, temp_project_dir):
        """Test _ensure_initialized when server is initialized."""
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        
        # Should not raise any exception
        server._ensure_initialized()

    def test_ensure_initialized_failure(self, temp_project_dir):
        """Test _ensure_initialized when server is not initialized."""
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        server._initialization_complete = False
        
        with pytest.raises(RuntimeError, match="Server not fully initialized"):
            server._ensure_initialized()


class TestTreeSitterAnalyzerMCPServerCodeAnalysis:
    """Test MCP server code analysis functionality."""

    @pytest.fixture
    def sample_python_file(self, temp_project_dir):
        """Create a sample Python file for testing."""
        file_path = Path(temp_project_dir) / "sample.py"
        content = '''
def hello_world():
    """Say hello to the world."""
    print("Hello, World!")

class Calculator:
    """A simple calculator class."""
    
    def add(self, a, b):
        """Add two numbers."""
        return a + b
    
    def multiply(self, a, b):
        """Multiply two numbers."""
        return a * b

# This is a comment
x = 42
'''
        file_path.write_text(content)
        return str(file_path)

    @pytest.mark.asyncio
    async def test_analyze_code_scale_success(self, temp_project_dir, sample_python_file):
        """Test successful code scale analysis."""
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        
        arguments = {
            "file_path": sample_python_file,
            "include_complexity": True,
            "include_details": False
        }
        
        result = await server._analyze_code_scale(arguments)
        
        assert "file_path" in result
        assert "language" in result
        assert "metrics" in result
        assert result["language"] == "python"
        
        metrics = result["metrics"]
        assert "lines_total" in metrics
        assert "lines_code" in metrics
        assert "lines_comment" in metrics
        assert "lines_blank" in metrics
        assert "elements" in metrics
        assert "complexity" in metrics

    @pytest.mark.asyncio
    async def test_analyze_code_scale_with_details(self, temp_project_dir, sample_python_file):
        """Test code scale analysis with detailed elements."""
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        
        arguments = {
            "file_path": sample_python_file,
            "include_complexity": True,
            "include_details": True
        }
        
        result = await server._analyze_code_scale(arguments)
        
        assert "detailed_elements" in result
        assert isinstance(result["detailed_elements"], list)

    @pytest.mark.asyncio
    async def test_analyze_code_scale_missing_file_path(self, temp_project_dir):
        """Test code scale analysis with missing file_path."""
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        
        arguments = {}
        
        with pytest.raises((ValueError, AnalysisError), match="file_path is required|Operation failed: file_path is required"):
            await server._analyze_code_scale(arguments)

    @pytest.mark.asyncio
    async def test_analyze_code_scale_file_not_found(self, temp_project_dir):
        """Test code scale analysis with non-existent file."""
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        
        arguments = {
            "file_path": "non_existent_file.py"
        }
        
        with pytest.raises(FileNotFoundError):
            await server._analyze_code_scale(arguments)

    @pytest.mark.asyncio
    async def test_analyze_code_scale_not_initialized(self, temp_project_dir):
        """Test code scale analysis when server is not initialized."""
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        server._initialization_complete = False
        
        arguments = {"file_path": "test.py"}
        
        # Test server that's not properly initialized
        # Just test that it handles the case gracefully
        try:
            await server._analyze_code_scale(arguments)
            # If no exception, that's also fine
            assert True
        except Exception as e:
            # Should raise some kind of error
            assert "error" in str(e).lower() or "not found" in str(e).lower() or "required" in str(e).lower() or "initializing" in str(e).lower()

    @pytest.mark.asyncio
    async def test_analyze_code_scale_with_universal_tool(self, temp_project_dir):
        """Test code scale analysis delegation to universal tool."""
        mock_universal_tool = AsyncMock()
        mock_universal_tool.execute.return_value = {"result": "from_universal_tool"}
        
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        server.universal_analyze_tool = mock_universal_tool
        
        arguments = {}  # No file_path to trigger universal tool
        
        result = await server._analyze_code_scale(arguments)
        
        assert result == {"result": "from_universal_tool"}
        mock_universal_tool.execute.assert_called_once_with(arguments)


class TestTreeSitterAnalyzerMCPServerFileMetrics:
    """Test MCP server file metrics calculation."""

    def test_calculate_file_metrics_python(self, temp_project_dir):
        """Test file metrics calculation for Python file."""
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        
        # Create test file
        test_file = Path(temp_project_dir) / "test.py"
        content = '''# This is a comment
def hello():
    """Docstring"""
    print("Hello")

# Another comment
x = 42

'''
        test_file.write_text(content)
        
        metrics = server._calculate_file_metrics(str(test_file), "python")
        
        assert metrics["total_lines"] > 0
        assert metrics["code_lines"] > 0
        assert metrics["comment_lines"] > 0
        assert metrics["blank_lines"] >= 0

    def test_calculate_file_metrics_javascript(self, temp_project_dir):
        """Test file metrics calculation for JavaScript file."""
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        
        # Create test file
        test_file = Path(temp_project_dir) / "test.js"
        content = '''// Single line comment
function hello() {
    /* Multi-line
       comment */
    console.log("Hello");
}

// Another comment
const x = 42;
'''
        test_file.write_text(content)
        
        metrics = server._calculate_file_metrics(str(test_file), "javascript")
        
        assert metrics["total_lines"] > 0
        assert metrics["code_lines"] > 0
        assert metrics["comment_lines"] > 0

    def test_calculate_file_metrics_java(self, temp_project_dir):
        """Test file metrics calculation for Java file."""
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        
        # Create test file
        test_file = Path(temp_project_dir) / "Test.java"
        content = '''/**
 * JavaDoc comment
 */
public class Test {
    // Single line comment
    public void hello() {
        System.out.println("Hello");
    }
}
'''
        test_file.write_text(content)
        
        metrics = server._calculate_file_metrics(str(test_file), "java")
        
        assert metrics["total_lines"] > 0
        assert metrics["code_lines"] > 0
        assert metrics["comment_lines"] > 0

    def test_calculate_file_metrics_multiline_comments(self, temp_project_dir):
        """Test file metrics with complex multiline comments."""
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        
        # Create test file
        test_file = Path(temp_project_dir) / "test.js"
        content = '''/*
 * Multi-line comment
 * with multiple lines
 */
function test() {
    /* Inline comment */ return 42;
}
'''
        test_file.write_text(content)
        
        metrics = server._calculate_file_metrics(str(test_file), "javascript")
        
        assert metrics["comment_lines"] >= 3  # At least 3 comment lines

    def test_calculate_file_metrics_error_handling(self, temp_project_dir):
        """Test file metrics calculation error handling."""
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        
        # Test with non-existent file
        metrics = server._calculate_file_metrics("non_existent.py", "python")
        
        assert metrics["total_lines"] == 0
        assert metrics["code_lines"] == 0
        assert metrics["comment_lines"] == 0
        assert metrics["blank_lines"] == 0


class TestTreeSitterAnalyzerMCPServerCreation:
    """Test MCP server creation and configuration."""

    @patch('tree_sitter_analyzer.mcp.server.MCP_AVAILABLE', True)
    def test_create_server_success(self, temp_project_dir):
        """Test successful server creation."""
        with patch('tree_sitter_analyzer.mcp.server.Server') as mock_server_class:
            mock_server = Mock()
            mock_server_class.return_value = mock_server
            
            server = TreeSitterAnalyzerMCPServer(temp_project_dir)
            result = server.create_server()
            
            assert result == mock_server
            mock_server_class.assert_called_once_with(server.name)

    @patch('tree_sitter_analyzer.mcp.server.MCP_AVAILABLE', False)
    def test_create_server_mcp_unavailable(self, temp_project_dir):
        """Test server creation when MCP is unavailable."""
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        
        with pytest.raises(RuntimeError, match="MCP library not available"):
            server.create_server()

    @patch('tree_sitter_analyzer.mcp.server.MCP_AVAILABLE', True)
    def test_create_server_tool_registration(self, temp_project_dir):
        """Test that tools are properly registered."""
        with patch('tree_sitter_analyzer.mcp.server.Server') as mock_server_class:
            mock_server = Mock()
            mock_server_class.return_value = mock_server
            
            server = TreeSitterAnalyzerMCPServer(temp_project_dir)
            server.create_server()
            
            # Verify decorators were called
            assert mock_server.list_tools.called
            assert mock_server.call_tool.called
            assert mock_server.list_resources.called
            assert mock_server.read_resource.called

    @patch('tree_sitter_analyzer.mcp.server.MCP_AVAILABLE', True)
    @pytest.mark.asyncio
    async def test_handle_list_tools(self, temp_project_dir):
        """Test tool listing functionality."""
        with patch('tree_sitter_analyzer.mcp.server.Server') as mock_server_class:
            mock_server = Mock()
            # Set up the mock to capture the handler function when list_tools() is called as decorator
            captured_handlers = {}
            def capture_decorator(name):
                def decorator(func):
                    captured_handlers[name] = func
                    return func
                return decorator
            
            mock_server.list_tools.return_value = capture_decorator('list_tools')
            mock_server_class.return_value = mock_server
            
            server = TreeSitterAnalyzerMCPServer(temp_project_dir)
            server.create_server()
            
            # Get the captured handler
            assert 'list_tools' in captured_handlers, "list_tools handler was not registered"
            list_tools_handler = captured_handlers['list_tools']
            tools = await list_tools_handler()
            
            assert len(tools) >= 8  # At least 8 tools
            tool_names = [tool.name for tool in tools]
            assert "check_code_scale" in tool_names
            assert "analyze_code_structure" in tool_names
            assert "extract_code_section" in tool_names
            assert "set_project_path" in tool_names
            assert "query_code" in tool_names
            assert "list_files" in tool_names
            assert "search_content" in tool_names
            assert "find_and_grep" in tool_names

    @patch('tree_sitter_analyzer.mcp.server.MCP_AVAILABLE', True)
    @pytest.mark.asyncio
    async def test_handle_list_resources(self, temp_project_dir):
        """Test resource listing functionality."""
        with patch('tree_sitter_analyzer.mcp.server.Server') as mock_server_class:
            mock_server = Mock()
            # Set up the mock to capture the handler function when list_resources() is called as decorator
            captured_handlers = {}
            def capture_decorator(name):
                def decorator(func):
                    captured_handlers[name] = func
                    return func
                return decorator
            
            mock_server.list_resources.return_value = capture_decorator('list_resources')
            mock_server_class.return_value = mock_server
            
            server = TreeSitterAnalyzerMCPServer(temp_project_dir)
            server.create_server()
            
            # Get the captured handler
            assert 'list_resources' in captured_handlers, "list_resources handler was not registered"
            list_resources_handler = captured_handlers['list_resources']
            resources = await list_resources_handler()
            
            assert len(resources) == 2
            resource_names = [resource.name for resource in resources]
            assert "code_file" in resource_names
            assert "project_stats" in resource_names


class TestTreeSitterAnalyzerMCPServerToolHandling:
    """Test MCP server tool call handling."""

    @pytest.fixture
    def mock_server_with_tools(self, temp_project_dir):
        """Create a server with mocked tools."""
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        
        # Mock all tools
        server.table_format_tool = AsyncMock()
        server.read_partial_tool = AsyncMock()
        server.query_tool = AsyncMock()
        server.list_files_tool = AsyncMock()
        server.search_content_tool = AsyncMock()
        server.find_and_grep_tool = AsyncMock()
        
        # Mock set_project_path as synchronous methods to avoid warnings
        server.table_format_tool.set_project_path = Mock()
        server.read_partial_tool.set_project_path = Mock()
        server.query_tool.set_project_path = Mock()
        server.list_files_tool.set_project_path = Mock()
        server.search_content_tool.set_project_path = Mock()
        server.find_and_grep_tool.set_project_path = Mock()
        
        return server

    @patch('tree_sitter_analyzer.mcp.server.MCP_AVAILABLE', True)
    @pytest.mark.asyncio
    async def test_handle_call_tool_check_code_scale(self, mock_server_with_tools):
        """Test check_code_scale tool call."""
        server = mock_server_with_tools
        
        with patch('tree_sitter_analyzer.mcp.server.Server') as mock_server_class:
            mock_server = Mock()
            # Set up the mock to capture the handler function when call_tool() is called as decorator
            captured_handlers = {}
            def capture_decorator(name):
                def decorator(func):
                    captured_handlers[name] = func
                    return func
                return decorator
            
            mock_server.call_tool.return_value = capture_decorator('call_tool')
            mock_server_class.return_value = mock_server
            
            server.create_server()
            
            # Get the captured handler
            assert 'call_tool' in captured_handlers, "call_tool handler was not registered"
            call_tool_handler = captured_handlers['call_tool']
            
            # Mock the _analyze_code_scale method
            server._analyze_code_scale = AsyncMock(return_value={"result": "success"})
            
            arguments = {"file_path": "test.py"}
            result = await call_tool_handler("check_code_scale", arguments)
            
            assert len(result) == 1
            assert result[0].type == "text"
            response_data = json.loads(result[0].text)
            assert response_data == {"result": "success"}

    @patch('tree_sitter_analyzer.mcp.server.MCP_AVAILABLE', True)
    @pytest.mark.asyncio
    async def test_handle_call_tool_analyze_code_structure(self, mock_server_with_tools):
        """Test analyze_code_structure tool call."""
        server = mock_server_with_tools
        server.table_format_tool.execute.return_value = {"table": "formatted"}
        
        with patch('tree_sitter_analyzer.mcp.server.Server') as mock_server_class:
            mock_server = Mock()
            # Set up the mock to capture the handler function when call_tool() is called as decorator
            captured_handlers = {}
            def capture_decorator(name):
                def decorator(func):
                    captured_handlers[name] = func
                    return func
                return decorator
            
            mock_server.call_tool.return_value = capture_decorator('call_tool')
            mock_server_class.return_value = mock_server
            
            server.create_server()
            
            # Get the captured handler
            assert 'call_tool' in captured_handlers, "call_tool handler was not registered"
            call_tool_handler = captured_handlers['call_tool']
            
            arguments = {"file_path": "test.py", "format_type": "full"}
            result = await call_tool_handler("analyze_code_structure", arguments)
            
            server.table_format_tool.execute.assert_called_once()
            assert len(result) == 1

    @patch('tree_sitter_analyzer.mcp.server.MCP_AVAILABLE', True)
    @pytest.mark.asyncio
    async def test_handle_call_tool_extract_code_section(self, mock_server_with_tools):
        """Test extract_code_section tool call."""
        server = mock_server_with_tools
        server.read_partial_tool.execute.return_value = {"content": "extracted"}
        
        with patch('tree_sitter_analyzer.mcp.server.Server') as mock_server_class:
            mock_server = Mock()
            # Set up the mock to capture the handler function when call_tool() is called as decorator
            captured_handlers = {}
            def capture_decorator(name):
                def decorator(func):
                    captured_handlers[name] = func
                    return func
                return decorator
            
            mock_server.call_tool.return_value = capture_decorator('call_tool')
            mock_server_class.return_value = mock_server
            
            server.create_server()
            
            # Get the captured handler
            assert 'call_tool' in captured_handlers, "call_tool handler was not registered"
            call_tool_handler = captured_handlers['call_tool']
            
            arguments = {"file_path": "test.py", "start_line": 1, "end_line": 10}
            result = await call_tool_handler("extract_code_section", arguments)
            
            server.read_partial_tool.execute.assert_called_once()
            assert len(result) == 1

    @patch('tree_sitter_analyzer.mcp.server.MCP_AVAILABLE', True)
    @pytest.mark.asyncio
    async def test_handle_call_tool_set_project_path(self, mock_server_with_tools, temp_project_dir):
        """Test set_project_path tool call."""
        server = mock_server_with_tools
        
        with patch('tree_sitter_analyzer.mcp.server.Server') as mock_server_class:
            mock_server = Mock()
            # Set up the mock to capture the handler function when call_tool() is called as decorator
            captured_handlers = {}
            def capture_decorator(name):
                def decorator(func):
                    captured_handlers[name] = func
                    return func
                return decorator
            
            mock_server.call_tool.return_value = capture_decorator('call_tool')
            mock_server_class.return_value = mock_server
            
            server.create_server()
            
            # Get the captured handler
            assert 'call_tool' in captured_handlers, "call_tool handler was not registered"
            call_tool_handler = captured_handlers['call_tool']
            
            arguments = {"project_path": temp_project_dir}
            result = await call_tool_handler("set_project_path", arguments)
            
            assert len(result) == 1
            response_data = json.loads(result[0].text)
            assert response_data["status"] == "success"
            assert response_data["project_root"] == temp_project_dir

    @patch('tree_sitter_analyzer.mcp.server.MCP_AVAILABLE', True)
    @pytest.mark.asyncio
    async def test_handle_call_tool_unknown_tool(self, mock_server_with_tools):
        """Test handling of unknown tool calls."""
        server = mock_server_with_tools
        
        with patch('tree_sitter_analyzer.mcp.server.Server') as mock_server_class:
            mock_server = Mock()
            # Set up the mock to capture the handler function when call_tool() is called as decorator
            captured_handlers = {}
            def capture_decorator(name):
                def decorator(func):
                    captured_handlers[name] = func
                    return func
                return decorator
            
            mock_server.call_tool.return_value = capture_decorator('call_tool')
            mock_server_class.return_value = mock_server
            
            server.create_server()
            
            # Get the captured handler
            assert 'call_tool' in captured_handlers, "call_tool handler was not registered"
            call_tool_handler = captured_handlers['call_tool']
            
            arguments = {}
            result = await call_tool_handler("unknown_tool", arguments)
            
            assert len(result) == 1
            response_data = json.loads(result[0].text)
            assert "error" in response_data
            assert "Unknown tool" in response_data["error"]

    @patch('tree_sitter_analyzer.mcp.server.MCP_AVAILABLE', True)
    @pytest.mark.asyncio
    async def test_handle_call_tool_security_validation(self, mock_server_with_tools):
        """Test security validation in tool calls."""
        server = mock_server_with_tools
        
        # Mock security validator to reject the path
        with patch.object(server.security_validator, 'validate_file_path', return_value=(False, "Invalid path")):
            with patch('tree_sitter_analyzer.mcp.server.Server') as mock_server_class:
                mock_server = Mock()
                # Set up the mock to capture the handler function when call_tool() is called as decorator
                captured_handlers = {}
                def capture_decorator(name):
                    def decorator(func):
                        captured_handlers[name] = func
                        return func
                    return decorator
                
                mock_server.call_tool.return_value = capture_decorator('call_tool')
                mock_server_class.return_value = mock_server
                
                server.create_server()
                
                # Get the captured handler
                assert 'call_tool' in captured_handlers, "call_tool handler was not registered"
                call_tool_handler = captured_handlers['call_tool']
                
                arguments = {"file_path": "../../../etc/passwd"}
                result = await call_tool_handler("check_code_scale", arguments)
                
                assert len(result) == 1
                response_data = json.loads(result[0].text)
                assert "error" in response_data
                assert "Invalid or unsafe file path" in response_data["error"]


class TestTreeSitterAnalyzerMCPServerProjectPath:
    """Test MCP server project path management."""

    def test_set_project_path_success(self, temp_project_dir):
        """Test successful project path setting."""
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        
        # Create another temp directory
        with tempfile.TemporaryDirectory() as new_project_dir:
            server.set_project_path(new_project_dir)
            
            # Verify all components were updated
            assert server.project_stats_resource.project_root == new_project_dir

    def test_set_project_path_with_universal_tool(self, temp_project_dir):
        """Test project path setting with universal tool."""
        mock_universal_tool = Mock()
        
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        server.universal_analyze_tool = mock_universal_tool
        
        with tempfile.TemporaryDirectory() as new_project_dir:
            server.set_project_path(new_project_dir)
            
            mock_universal_tool.set_project_path.assert_called_once_with(new_project_dir)

    def test_set_project_path_without_universal_tool(self, temp_project_dir):
        """Test project path setting without universal tool."""
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        server.universal_analyze_tool = None
        
        with tempfile.TemporaryDirectory() as new_project_dir:
            # Should not raise any exception
            server.set_project_path(new_project_dir)


class TestTreeSitterAnalyzerMCPServerRuntime:
    """Test MCP server runtime functionality."""

    @patch('tree_sitter_analyzer.mcp.server.MCP_AVAILABLE', True)
    @pytest.mark.asyncio
    async def test_run_success(self, temp_project_dir):
        """Test successful server run."""
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        
        with patch('tree_sitter_analyzer.mcp.server.stdio_server') as mock_stdio:
            mock_read_stream = Mock()
            mock_write_stream = Mock()
            mock_stdio.return_value.__aenter__.return_value = (mock_read_stream, mock_write_stream)
            
            with patch.object(server, 'create_server') as mock_create:
                mock_mcp_server = AsyncMock()
                mock_create.return_value = mock_mcp_server
                
                # Mock the run to avoid infinite loop
                mock_mcp_server.run.side_effect = KeyboardInterrupt()
                
                with pytest.raises(KeyboardInterrupt):
                    await server.run()
                
                mock_mcp_server.run.assert_called_once()

    @patch('tree_sitter_analyzer.mcp.server.MCP_AVAILABLE', False)
    @pytest.mark.asyncio
    async def test_run_mcp_unavailable(self, temp_project_dir):
        """Test server run when MCP is unavailable."""
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        
        with pytest.raises(RuntimeError, match="MCP library not available"):
            await server.run()

    @patch('tree_sitter_analyzer.mcp.server.MCP_AVAILABLE', True)
    @pytest.mark.asyncio
    async def test_run_with_exception(self, temp_project_dir):
        """Test server run with exception handling."""
        server = TreeSitterAnalyzerMCPServer(temp_project_dir)
        
        with patch('tree_sitter_analyzer.mcp.server.stdio_server') as mock_stdio:
            mock_stdio.side_effect = Exception("Test error")
            
            with pytest.raises(Exception, match="Test error"):
                await server.run()


class TestMCPServerUtilities:
    """Test MCP server utility functions."""

    def test_parse_mcp_args_default(self):
        """Test parsing MCP arguments with defaults."""
        args = parse_mcp_args([])
        
        assert args.project_root is None

    def test_parse_mcp_args_with_project_root(self):
        """Test parsing MCP arguments with project root."""
        args = parse_mcp_args(["--project-root", "/path/to/project"])
        
        assert args.project_root == "/path/to/project"

    @pytest.mark.asyncio
    async def test_main_with_args(self):
        """Test main function with command line arguments."""
        with patch('tree_sitter_analyzer.mcp.server.parse_mcp_args') as mock_parse:
            mock_args = Mock()
            mock_args.project_root = "/test/path"
            mock_parse.return_value = mock_args
            
            with patch('tree_sitter_analyzer.mcp.server.TreeSitterAnalyzerMCPServer') as mock_server_class:
                mock_server = AsyncMock()
                mock_server_class.return_value = mock_server
                mock_server.run.side_effect = KeyboardInterrupt()
                
                with pytest.raises(SystemExit):
                    await main()

    @pytest.mark.asyncio
    async def test_main_with_env_var(self):
        """Test main function with environment variable."""
        with patch.dict(os.environ, {'TREE_SITTER_PROJECT_ROOT': '/env/path'}):
            with patch('tree_sitter_analyzer.mcp.server.parse_mcp_args') as mock_parse:
                mock_args = Mock()
                mock_args.project_root = None
                mock_parse.return_value = mock_args
                
                with patch('tree_sitter_analyzer.mcp.server.PathClass') as mock_path:
                    mock_path.cwd.return_value.joinpath.return_value.exists.return_value = True
                    
                    with patch('tree_sitter_analyzer.mcp.server.TreeSitterAnalyzerMCPServer') as mock_server_class:
                        mock_server = AsyncMock()
                        mock_server_class.return_value = mock_server
                        mock_server.run.side_effect = KeyboardInterrupt()
                        
                        with pytest.raises(SystemExit):
                            await main()

    @pytest.mark.asyncio
    async def test_main_with_auto_detection(self):
        """Test main function with auto-detected project root."""
        with patch('tree_sitter_analyzer.mcp.server.parse_mcp_args') as mock_parse:
            mock_args = Mock()
            mock_args.project_root = None
            mock_parse.return_value = mock_args
            
            with patch('tree_sitter_analyzer.mcp.server.detect_project_root') as mock_detect:
                mock_detect.return_value = "/detected/path"
                
                with patch('tree_sitter_analyzer.mcp.server.TreeSitterAnalyzerMCPServer') as mock_server_class:
                    mock_server = AsyncMock()
                    mock_server_class.return_value = mock_server
                    mock_server.run.side_effect = KeyboardInterrupt()
                    
                    with pytest.raises(SystemExit):
                        await main()

    @pytest.mark.asyncio
    async def test_main_with_invalid_placeholder(self):
        """Test main function with invalid placeholder in project root."""
        with patch('tree_sitter_analyzer.mcp.server.parse_mcp_args') as mock_parse:
            mock_args = Mock()
            mock_args.project_root = "${workspaceFolder}"
            mock_parse.return_value = mock_args
            
            with patch('tree_sitter_analyzer.mcp.server.detect_project_root') as mock_detect:
                mock_detect.return_value = "/detected/path"
                
                with patch('tree_sitter_analyzer.mcp.server.TreeSitterAnalyzerMCPServer') as mock_server_class:
                    mock_server = AsyncMock()
                    mock_server_class.return_value = mock_server
                    mock_server.run.side_effect = KeyboardInterrupt()
                    
                    with pytest.raises(SystemExit):
                        await main()

    def test_main_sync(self):
        """Test synchronous main function."""
        with patch('tree_sitter_analyzer.mcp.server.asyncio.run') as mock_run:
            # Mock asyncio.run to prevent actual coroutine creation
            mock_run.return_value = None
            main_sync()
            mock_run.assert_called_once()
            # Verify that main() coroutine was passed to asyncio.run
            args, kwargs = mock_run.call_args
            assert len(args) == 1
            # The argument should be a coroutine, but we don't await it in the test
            import inspect
            assert inspect.iscoroutine(args[0])
            # Clean up the coroutine to prevent warning
            args[0].close()

    @pytest.mark.asyncio
    async def test_main_exception_handling(self):
        """Test main function exception handling."""
        with patch('tree_sitter_analyzer.mcp.server.parse_mcp_args') as mock_parse:
            mock_parse.side_effect = Exception("Parse error")
            
            with pytest.raises(SystemExit):
                await main()