#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Tests for tree_sitter_analyzer.interfaces.mcp_server module.

Comprehensive test suite for the MCP server interface functionality.
"""

import asyncio
import json
import pytest
from unittest.mock import AsyncMock, MagicMock, Mock, patch
from typing import Any, Dict, List

from tree_sitter_analyzer.interfaces.mcp_server import (
    TreeSitterAnalyzerMCPServer,
    MCP_AVAILABLE,
    main
)


class TestMCPAvailability:
    """Test MCP library availability detection."""
    
    def test_mcp_available_constant(self):
        """Test MCP_AVAILABLE constant is properly set."""
        # This will be True if mcp is installed, False otherwise
        assert isinstance(MCP_AVAILABLE, bool)


class TestTreeSitterAnalyzerMCPServerInitialization:
    """Test MCP server initialization."""
    
    @patch('tree_sitter_analyzer.interfaces.mcp_server.MCP_AVAILABLE', True)
    @patch('tree_sitter_analyzer.interfaces.mcp_server.log_info')
    def test_server_initialization_success(self, mock_log_info):
        """Test successful server initialization when MCP is available."""
        server = TreeSitterAnalyzerMCPServer()
        
        assert server.server is None
        assert server.name == "tree-sitter-analyzer"
        assert server.version == "2.0.0"
        mock_log_info.assert_called_once_with("Initializing tree-sitter-analyzer v2.0.0")
    
    @patch('tree_sitter_analyzer.interfaces.mcp_server.MCP_AVAILABLE', False)
    def test_server_initialization_mcp_unavailable(self):
        """Test server initialization fails when MCP is unavailable."""
        with pytest.raises(ImportError, match="MCP library not available"):
            TreeSitterAnalyzerMCPServer()


class TestMCPServerCreation:
    """Test MCP server creation and configuration."""
    
    @patch('tree_sitter_analyzer.interfaces.mcp_server.MCP_AVAILABLE', True)
    @patch('tree_sitter_analyzer.interfaces.mcp_server.Server')
    @patch('tree_sitter_analyzer.interfaces.mcp_server.log_info')
    def test_create_server_success(self, mock_log_info, mock_server_class):
        """Test successful server creation."""
        mock_server = Mock()
        mock_server_class.return_value = mock_server
        
        server = TreeSitterAnalyzerMCPServer()
        result = server.create_server()
        
        assert result == mock_server
        assert server.server == mock_server
        mock_server_class.assert_called_once_with("tree-sitter-analyzer")
        mock_log_info.assert_any_call("MCP server created successfully")
    
    @patch('tree_sitter_analyzer.interfaces.mcp_server.MCP_AVAILABLE', True)
    @patch('tree_sitter_analyzer.interfaces.mcp_server.Server')
    def test_server_tool_registration(self, mock_server_class):
        """Test that tools are properly registered."""
        mock_server = Mock()
        mock_server_class.return_value = mock_server
        
        server = TreeSitterAnalyzerMCPServer()
        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


class TestServerProperties:
    """Test server properties and basic functionality."""
    
    @patch('tree_sitter_analyzer.interfaces.mcp_server.MCP_AVAILABLE', True)
    def test_server_properties(self):
        """Test server properties are set correctly."""
        server = TreeSitterAnalyzerMCPServer()
        
        assert server.name == "tree-sitter-analyzer"
        assert server.version == "2.0.0"
        assert server.server is None
    
    @patch('tree_sitter_analyzer.interfaces.mcp_server.MCP_AVAILABLE', True)
    @patch('tree_sitter_analyzer.interfaces.mcp_server.Server')
    def test_server_creation_sets_server_property(self, mock_server_class):
        """Test that create_server sets the server property."""
        mock_server = Mock()
        mock_server_class.return_value = mock_server
        
        server = TreeSitterAnalyzerMCPServer()
        assert server.server is None
        
        server.create_server()
        assert server.server == mock_server


class TestMainFunctionBasic:
    """Test main function basic functionality."""
    
    @patch('tree_sitter_analyzer.interfaces.mcp_server.TreeSitterAnalyzerMCPServer')
    @patch('tree_sitter_analyzer.interfaces.mcp_server.log_info')
    @patch('tree_sitter_analyzer.interfaces.mcp_server.log_error')
    def test_main_keyboard_interrupt(self, mock_log_error, mock_log_info, mock_server_class):
        """Test main function handles KeyboardInterrupt."""
        mock_server = Mock()
        mock_server.run = AsyncMock(side_effect=KeyboardInterrupt())
        mock_server_class.return_value = mock_server
        
        # Run main in event loop
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        try:
            loop.run_until_complete(main())
        finally:
            loop.close()
        
        mock_server_class.assert_called_once()
        mock_log_info.assert_called_with("Server stopped by user")
        mock_log_error.assert_not_called()
    
    @patch('tree_sitter_analyzer.interfaces.mcp_server.TreeSitterAnalyzerMCPServer')
    @patch('tree_sitter_analyzer.interfaces.mcp_server.log_error')
    def test_main_exception_handling(self, mock_log_error, mock_server_class):
        """Test main function handles exceptions."""
        mock_server_class.side_effect = Exception("Test error")
        
        # Run main in event loop and expect SystemExit
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        try:
            with pytest.raises(SystemExit):
                loop.run_until_complete(main())
        finally:
            loop.close()
        
        mock_log_error.assert_called()


class TestServerRunMethod:
    """Test server run method."""
    
    @patch('tree_sitter_analyzer.interfaces.mcp_server.MCP_AVAILABLE', True)
    @patch('tree_sitter_analyzer.interfaces.mcp_server.stdio_server')
    @patch('tree_sitter_analyzer.interfaces.mcp_server.InitializationOptions')
    @patch('tree_sitter_analyzer.interfaces.mcp_server.Server')
    @patch('tree_sitter_analyzer.interfaces.mcp_server.log_info')
    def test_run_method_basic(self, mock_log_info, mock_server_class, mock_init_options, mock_stdio_server):
        """Test run method basic functionality."""
        # Use regular Mock instead of AsyncMock for server decorators
        mock_server = Mock()
        mock_server.run = AsyncMock()
        mock_server_class.return_value = mock_server
        
        # Mock stdio_server context manager to complete quickly
        mock_streams = (AsyncMock(), AsyncMock())
        mock_stdio_server.return_value.__aenter__ = AsyncMock(return_value=mock_streams)
        mock_stdio_server.return_value.__aexit__ = AsyncMock(return_value=None)
        
        # Make server.run complete quickly
        async def quick_run(*args, **kwargs):
            await asyncio.sleep(0.01)  # Very short delay
            
        mock_server.run.side_effect = quick_run
        
        server = TreeSitterAnalyzerMCPServer()
        
        # Test run method
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        try:
            loop.run_until_complete(server.run())
        finally:
            loop.close()
        
        mock_log_info.assert_any_call("Starting MCP server: tree-sitter-analyzer v2.0.0")
        mock_init_options.assert_called_once()


class TestErrorHandling:
    """Test error handling scenarios."""
    
    @patch('tree_sitter_analyzer.interfaces.mcp_server.MCP_AVAILABLE', True)
    @patch('tree_sitter_analyzer.interfaces.mcp_server.stdio_server')
    @patch('tree_sitter_analyzer.interfaces.mcp_server.Server')
    @patch('tree_sitter_analyzer.interfaces.mcp_server.log_error')
    def test_run_method_exception_handling(self, mock_log_error, mock_server_class, mock_stdio_server):
        """Test run method handles exceptions."""
        # Use regular Mock instead of AsyncMock for server decorators
        mock_server = Mock()
        mock_server.run = AsyncMock()
        mock_server_class.return_value = mock_server
        
        # Make stdio_server raise an exception
        mock_stdio_server.side_effect = Exception("Server error")
        
        server = TreeSitterAnalyzerMCPServer()
        
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        try:
            with pytest.raises(Exception, match="Server error"):
                loop.run_until_complete(server.run())
        finally:
            loop.close()
        
        mock_log_error.assert_called()


class TestLoggingIntegration:
    """Test logging integration."""
    
    @patch('tree_sitter_analyzer.interfaces.mcp_server.MCP_AVAILABLE', True)
    @patch('tree_sitter_analyzer.interfaces.mcp_server.log_info')
    def test_initialization_logging(self, mock_log_info):
        """Test that initialization logs correctly."""
        server = TreeSitterAnalyzerMCPServer()
        mock_log_info.assert_called_once_with("Initializing tree-sitter-analyzer v2.0.0")
    
    @patch('tree_sitter_analyzer.interfaces.mcp_server.MCP_AVAILABLE', True)
    @patch('tree_sitter_analyzer.interfaces.mcp_server.Server')
    @patch('tree_sitter_analyzer.interfaces.mcp_server.log_info')
    def test_server_creation_logging(self, mock_log_info, mock_server_class):
        """Test that server creation logs correctly."""
        mock_server = Mock()
        mock_server_class.return_value = mock_server
        
        server = TreeSitterAnalyzerMCPServer()
        server.create_server()
        
        mock_log_info.assert_any_call("MCP server created successfully")


class TestModuleImports:
    """Test module imports and dependencies."""
    
    def test_module_imports(self):
        """Test that required modules can be imported."""
        # Test that the module can be imported
        from tree_sitter_analyzer.interfaces import mcp_server
        assert hasattr(mcp_server, 'TreeSitterAnalyzerMCPServer')
        assert hasattr(mcp_server, 'MCP_AVAILABLE')
        assert hasattr(mcp_server, 'main')
    
    def test_mcp_availability_detection(self):
        """Test MCP availability detection logic."""
        # The MCP_AVAILABLE constant should be a boolean
        assert isinstance(MCP_AVAILABLE, bool)
        
        # If MCP is available, we should be able to create a server
        if MCP_AVAILABLE:
            with patch('tree_sitter_analyzer.interfaces.mcp_server.log_info'):
                server = TreeSitterAnalyzerMCPServer()
                assert server.name == "tree-sitter-analyzer"


class TestServerConfiguration:
    """Test server configuration and setup."""
    
    @patch('tree_sitter_analyzer.interfaces.mcp_server.MCP_AVAILABLE', True)
    @patch('tree_sitter_analyzer.interfaces.mcp_server.Server')
    def test_server_name_configuration(self, mock_server_class):
        """Test server name is configured correctly."""
        mock_server = Mock()
        mock_server_class.return_value = mock_server
        
        server = TreeSitterAnalyzerMCPServer()
        server.create_server()
        
        mock_server_class.assert_called_once_with("tree-sitter-analyzer")
    
    @patch('tree_sitter_analyzer.interfaces.mcp_server.MCP_AVAILABLE', True)
    @patch('tree_sitter_analyzer.interfaces.mcp_server.InitializationOptions')
    @patch('tree_sitter_analyzer.interfaces.mcp_server.Server')
    def test_initialization_options_configuration(self, mock_server_class, mock_init_options):
        """Test initialization options are configured correctly."""
        # Use regular Mock instead of AsyncMock for server decorators
        mock_server = Mock()
        mock_server.run = AsyncMock()
        mock_server_class.return_value = mock_server
        
        server = TreeSitterAnalyzerMCPServer()
        
        # Mock stdio_server to avoid actual server startup
        with patch('tree_sitter_analyzer.interfaces.mcp_server.stdio_server') as mock_stdio:
            mock_streams = (AsyncMock(), AsyncMock())
            mock_stdio.return_value.__aenter__ = AsyncMock(return_value=mock_streams)
            mock_stdio.return_value.__aexit__ = AsyncMock(return_value=None)
            
            # Make server.run complete quickly
            async def quick_run(*args, **kwargs):
                await asyncio.sleep(0.01)  # Very short delay
                
            mock_server.run.side_effect = quick_run
            
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            try:
                loop.run_until_complete(server.run())
            finally:
                loop.close()
        
        # Verify InitializationOptions was called with correct parameters
        mock_init_options.assert_called_once()
        call_args = mock_init_options.call_args[1]
        assert call_args['server_name'] == "tree-sitter-analyzer"
        assert call_args['server_version'] == "2.0.0"
        assert 'capabilities' in call_args


class TestEdgeCases:
    """Test edge cases and boundary conditions."""
    
    @patch('tree_sitter_analyzer.interfaces.mcp_server.MCP_AVAILABLE', True)
    def test_multiple_server_instances(self):
        """Test creating multiple server instances."""
        with patch('tree_sitter_analyzer.interfaces.mcp_server.log_info'):
            server1 = TreeSitterAnalyzerMCPServer()
            server2 = TreeSitterAnalyzerMCPServer()
            
            assert server1.name == server2.name
            assert server1.version == server2.version
            assert server1 is not server2
    
    @patch('tree_sitter_analyzer.interfaces.mcp_server.MCP_AVAILABLE', True)
    @patch('tree_sitter_analyzer.interfaces.mcp_server.Server')
    def test_create_server_multiple_calls(self, mock_server_class):
        """Test calling create_server multiple times."""
        mock_server1 = Mock()
        mock_server2 = Mock()
        mock_server_class.side_effect = [mock_server1, mock_server2]
        
        server = TreeSitterAnalyzerMCPServer()
        
        result1 = server.create_server()
        assert result1 == mock_server1
        assert server.server == mock_server1
        
        result2 = server.create_server()
        assert result2 == mock_server2
        assert server.server == mock_server2  # Should update to new server
        
        assert mock_server_class.call_count == 2


class TestAsyncBehavior:
    """Test async behavior and coroutines."""
    
    def test_main_is_coroutine(self):
        """Test that main function is a coroutine."""
        import inspect
        assert inspect.iscoroutinefunction(main)
    
    @patch('tree_sitter_analyzer.interfaces.mcp_server.MCP_AVAILABLE', True)
    def test_run_is_coroutine(self):
        """Test that run method is a coroutine."""
        import inspect
        server = TreeSitterAnalyzerMCPServer()
        assert inspect.iscoroutinefunction(server.run)


class TestIntegrationScenarios:
    """Test integration scenarios."""
    
    @patch('tree_sitter_analyzer.interfaces.mcp_server.MCP_AVAILABLE', True)
    @patch('tree_sitter_analyzer.interfaces.mcp_server.Server')
    @patch('tree_sitter_analyzer.interfaces.mcp_server.log_info')
    def test_full_server_lifecycle(self, mock_log_info, mock_server_class):
        """Test full server lifecycle from creation to setup."""
        mock_server = Mock()
        mock_server_class.return_value = mock_server
        
        # Create server instance
        server = TreeSitterAnalyzerMCPServer()
        assert server.server is None
        
        # Create MCP server
        result = server.create_server()
        assert result == mock_server
        assert server.server == mock_server
        
        # Verify all expected calls were made
        mock_server_class.assert_called_once_with("tree-sitter-analyzer")
        mock_log_info.assert_any_call("Initializing tree-sitter-analyzer v2.0.0")
        mock_log_info.assert_any_call("MCP server created successfully")
        
        # Verify decorators were registered
        assert mock_server.list_tools.called
        assert mock_server.call_tool.called
        assert mock_server.list_resources.called
        assert mock_server.read_resource.called