import json

from fastmcp import FastMCP
from wizlib.parser import WizParser

from dyngle.command import DyngleCommand
from dyngle.model.context import Context
from dyngle.model.operation import OperationAccess
from dyngle.error import DyngleError


class McpCommand(DyngleCommand):
    """Run an MCP server exposing Dyngle operations as tools"""

    name = "mcp"

    @classmethod
    def add_args(cls, parser: WizParser):
        super().add_args(parser)
        parser.add_argument(
            "--host", default="127.0.0.1", help="Host to bind the server to"
        )
        parser.add_argument(
            "--port", type=int, default=8000, help="Port to bind the server to"
        )
        parser.add_argument(
            "--transport",
            default="stdio",
            choices=["stdio", "http", "sse"],
            help="Transport protocol to use",
        )

    def create_server(self) -> FastMCP:
        """Create and configure the FastMCP server with tools for each
        operation"""
        mcp = FastMCP("Dyngle MCP Server")

        # Register each public operation as a tool
        for op_name, operation in self.app.dyngleverse.operations.items():
            # Skip private operations
            if operation.access != OperationAccess.PUBLIC:
                continue
            # Create a closure to capture the operation name and instance
            def make_tool(name: str, op):
                async def tool_func(
                    data: dict = None, args: list = None
                ) -> str:
                    """Execute a Dyngle operation

                    Args:
                        data: Input data dictionary to pass to the operation
                        args: List of arguments to pass to the operation

                    Returns:
                        JSON string with result or error
                    """
                    data = data or {}
                    args = args or []

                    # Create context from data
                    context = Context(data)

                    try:
                        # Execute the operation directly
                        # stdout/stderr flow naturally to wherever the
                        # calling app logs them
                        return_value = op.run(context, args)

                        # Return JSON with the result
                        return json.dumps({"result": return_value})

                    except DyngleError as e:
                        # Return JSON with error info
                        return json.dumps({"error": str(e)})
                    except Exception as e:
                        # Return JSON with error info for unexpected errors
                        error_msg = (
                            f"Unexpected error: {type(e).__name__}: {str(e)}"
                        )
                        return json.dumps({"error": error_msg})

                # Set the function name for the tool
                tool_func.__name__ = name
                return tool_func

            # Register the tool
            tool = make_tool(op_name, operation)
            mcp.tool(tool)

        return mcp

    @DyngleCommand.wrap
    def execute(self):
        """Start the MCP server"""
        server = self.create_server()

        # Build run arguments based on transport
        run_kwargs = {"transport": self.transport}
        
        # Only add host and port for non-stdio transports
        if self.transport != "stdio":
            run_kwargs["host"] = self.host
            run_kwargs["port"] = self.port

        # Run the server with the specified transport
        server.run(**run_kwargs)

        # Realistically we don't ever get here.
        self.status = f"MCP server started on {self.transport}"
