import logging
import sys
from functools import cached_property
from typing import Any, Dict, List

from mcp.server import InitializationOptions, Server
from mcp.server.stdio import stdio_server
from mcp.types import (
    ServerCapabilities,
    Tool,
    TextContent,
    ToolsCapability,
)

from .handlers.tools.tool_handler import DynamicToolHandler
from .custom_fastmcp_server import FastMCP
from bubobot_mcp.src.utils.singleton import BaseSingleton
from bubobot_mcp.src.utils.custom_error import CustomError
from bubobot_mcp.src.constants.error_codes import (
    StdioErrorCodes,
    HTTPAppErrorCodes,
)
from bubobot_mcp.src.config.config import Config


class MCPServer(BaseSingleton):
    """
    Core MCP (Model Context Protocol) server implementation.
    
    Handles client connections, message routing, and protocol compliance.
    Manages tool registration for AI model integration.
    Supports both stdio and StreamableHTTP transport.
    """
    
    def __init__(self, config: Config):
        """
        Initialize the MCP server with configuration.
        
        Args:
            config: Configuration object for the server
        """
        self.config = config
        self.logger = logging.getLogger(self.__class__.__name__)
        
        # Initialize MCP server
        self.server = Server(
            name=self.config.mcp_server_name,
            version=self.config.mcp_server_version,
        )

        # Initialize tool handler
        self.tool_handler = DynamicToolHandler(config=config)

        # Setup MCP handlers
        self.setup_handlers()

    @cached_property
    def _init_options(self) -> InitializationOptions:
        """Initialize server options."""
        # Configure server capabilities
        capabilities = ServerCapabilities(
            tools=ToolsCapability(listChanged=True)
        )
        
        # Initialize server options
        init_options = InitializationOptions(
            server_name=self.config.mcp_server_name,
            server_version=self.config.mcp_server_version,
            capabilities=capabilities
        )
        return init_options
    

    def setup_handlers(self) -> None:
        """Setup MCP protocol handlers for tools via stdio."""
        
        @self.server.list_tools()
        async def handle_list_tools() -> List[Tool]:
            """
            List all available tools for MCP clients.
            
            Returns:
                List[Tool]: Available tools for AI integration
            """
            self.logger.info("Listing available tools via stdio")
            try:
                tools = await self.tool_handler.list_tools()

                self.logger.info(f"Listed {len(tools)} tools successfully")
                return tools
            except Exception as e:
                error_message = f"Error listing tools: {e}"
                self.logger.error(error_message)
                return []
        
        @self.server.call_tool()
        async def handle_call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
            """
            Call a specific tool via stdio.
            
            Args:
                name: Tool name to execute
                arguments: Tool arguments
                
            Returns:
                List[TextContent]: Tool execution result
            """
            self.logger.info(f"Calling tool: {name} with args: {arguments}")
            try:
                # Execute tool
                result = await self.tool_handler.call_tool(name, arguments)
                
                # Convert result to text content
                if isinstance(result, str):
                    content = result
                else:
                    content = str(result)
                
                self.logger.info(f"Tool {name} executed successfully")
                return [TextContent(type="text", text=content)]
                
            except ValueError as e:
                # Tool not found
                self.logger.error(f"{e}")
                return [TextContent(type="text", text=f"{e}")]
                
            except Exception as e:
                # Other execution errors
                error_message = f"Error executing tool {name}: {e}"
                self.logger.error(error_message)
                return [TextContent(type="text", text=f"Execution error, please try again later")]

    async def run_stdio(self) -> None:
        """
        Run the MCP server using stdio transport.
        
        This is the standard MCP transport for AI model integration.
        """
        self.logger.info("Starting MCP server with stdio transport...")
        
        try:
            # Check if running in interactive mode
            if sys.stdin.isatty():
                self.logger.warning("Server started in interactive mode")
                self.logger.info("Waiting for MCP client connection via stdio...")
            else:
                self.logger.info("Server started in non-interactive mode")
            
            # Start stdio server
            async with stdio_server() as (read_stream, write_stream):
                self.logger.info("MCP server ready - listening for connections...")
                
                await self.server.run(
                    read_stream,
                    write_stream,
                    initialization_options=self._init_options
                )
                
        except KeyboardInterrupt:
            self.logger.info("Server shutdown requested by user")
            raise
            
        except Exception as e:
            error_message = f"Stdio server error: {e}"
            self.logger.error(error_message)
            raise CustomError(
                code=StdioErrorCodes.INIT_STDIO_SERVER_ERROR,
                message=error_message
            )
        
        finally:
            self.logger.info("Stdio server stopped")

    def setup_fast_server(self) -> None:
        """Setup FastMCP server with tools for HTTP transport."""
        try:
            self.fast_server = FastMCP(
                tool_handler=self.tool_handler,
                name=self.config.mcp_server_name,
                host=self.config.http_host,
                port=self.config.http_port,
            )

        except Exception as e:
            error_message = f"Error setting up FastMCP server: {e}"
            self.logger.error(error_message)
            raise CustomError(
                code=HTTPAppErrorCodes.INIT_FASTMCP_ERROR,
                message=error_message
            )

    async def run_http(self) -> None:
        """
        Run the MCP server using HTTP transport for multiple concurrent clients.

        """
        self.logger.info(f"Starting MCP server with HTTP transport...")
        
        try:
            # Setup FastMCP server
            self.setup_fast_server()
            
            self.logger.info(f"MCP server ready - listening for HTTP connections...")
            
            # Start HTTP server using FastMCP (async)
            await self.fast_server.run_streamable_http_async(
                ssl_keyfile=self.config.ssl_keyfile,
                ssl_certfile=self.config.ssl_certfile,
            )
                
        except KeyboardInterrupt:
            self.logger.info("Server shutdown requested by user")
            raise
            
        except Exception as e:
            error_message = f"HTTP server error: {e}"
            self.logger.error(error_message)
            raise CustomError(
                code=HTTPAppErrorCodes.INIT_HTTP_SERVER_ERROR,
                message=error_message
            )
        
        finally:
            self.logger.info("HTTP server stopped")
