#!/usr/bin/env python3
# SPDX-License-Identifier: AGPL-3.0-or-later

"""
Base server adapter for MCP testing.

This module defines the base server adapter class that will be extended
by specific server implementations.
"""

import asyncio
import json
import logging
from abc import ABC, abstractmethod
from typing import Dict, Any, List, Optional, Tuple, Union

logger = logging.getLogger(__name__)


class MCPServerAdapter(ABC):
    """
    Base class for MCP server adapters.
    
    Server adapters are responsible for starting, communicating with, and stopping
    MCP servers during testing. This abstract base class defines the interface that
    all server adapters must implement.
    """
    
    def __init__(self, protocol_version: str, debug: bool = False):
        """
        Initialize a server adapter.
        
        Args:
            protocol_version: The MCP protocol version to use
            debug: Whether to enable debug logging
        """
        self.protocol_version = protocol_version
        self.debug = debug
        self.server_info = None
        self._request_id = 0
        
    @abstractmethod
    async def start(self) -> bool:
        """
        Start the server.
        
        Returns:
            True if started successfully, False otherwise
        """
        pass
        
    @abstractmethod
    async def stop(self) -> bool:
        """
        Stop the server.
        
        Returns:
            True if stopped successfully, False otherwise
        """
        pass
        
    @abstractmethod
    async def send_request(self, method: str, params: Dict[str, Any] = None) -> Dict[str, Any]:
        """
        Send a request to the server and wait for a response.
        
        Args:
            method: The JSON-RPC method name
            params: The method parameters
            
        Returns:
            The server's response
            
        Raises:
            RuntimeError: If the server is not started or the request fails
        """
        pass
        
    @abstractmethod
    async def send_notification(self, method: str, params: Dict[str, Any] = None) -> None:
        """
        Send a notification to the server (no response expected).
        
        Args:
            method: The JSON-RPC method name
            params: The method parameters
            
        Raises:
            RuntimeError: If the server is not started or the notification fails
        """
        pass
        
    async def initialize(self) -> Dict[str, Any]:
        """
        Initialize the server.
        
        This sends the standard initialize request to the server.
        
        Returns:
            The server's initialization response
            
        Raises:
            RuntimeError: If initialization fails
        """
        params = {
            "protocolVersion": self.protocol_version,
            "options": {}
        }
        
        response = await self.send_request("initialize", params)
        
        if "error" in response:
            error_msg = response.get("error", {}).get("message", "Unknown error")
            logger.error(f"Server initialization failed: {error_msg}")
            raise RuntimeError(f"Failed to initialize server: {error_msg}")
            
        if "result" not in response:
            raise RuntimeError("Invalid initialize response, missing 'result' field")
            
        self.server_info = response["result"]
        return response
        
    async def shutdown(self) -> Optional[Dict[str, Any]]:
        """
        Send a shutdown request to the server.
        
        Returns:
            The server's shutdown response, or None if the server doesn't support shutdown
        """
        try:
            response = await self.send_request("shutdown", {})
            await self.send_notification("exit")
            return response
        except Exception as e:
            logger.warning(f"Failed to shut down server: {str(e)}")
            return None
            
    async def list_tools(self) -> List[Dict[str, Any]]:
        """
        Get the list of available tools from the server.
        
        Returns:
            A list of tool definitions
            
        Raises:
            RuntimeError: If the request fails
        """
        response = await self.send_request("tools/list", {})
        
        if "error" in response:
            error_msg = response.get("error", {}).get("message", "Unknown error")
            raise RuntimeError(f"Failed to list tools: {error_msg}")
            
        if "result" not in response:
            raise RuntimeError("Invalid tools/list response, missing 'result' field")
            
        return response["result"]
        
    async def call_tool(self, name: str, params: Dict[str, Any]) -> Dict[str, Any]:
        """
        Call a tool on the server.
        
        Args:
            name: The name of the tool to call
            params: The tool parameters
            
        Returns:
            The tool's response
            
        Raises:
            RuntimeError: If the tool call fails
        """
        request_params = {
            "name": name,
            "params": params
        }
        
        response = await self.send_request("callTool", request_params)
        
        if "error" in response:
            error = response.get("error", {})
            error_msg = error.get("message", "Unknown error")
            logger.error(f"Tool call failed: {error_msg}")
            return response
            
        if "result" not in response:
            logger.error("Invalid callTool response, missing 'result' field")
            
        return response
        
    def _get_next_request_id(self) -> int:
        """
        Get the next request ID.
        
        Returns:
            The next request ID
        """
        self._request_id += 1
        return self._request_id 