from __future__ import annotations

import asyncio
import json
import shutil
from typing import Any

from mcp import Tool as MCPTool
from mcp.types import (
    CallToolResult,
    Content,
    GetPromptResult,
    ListPromptsResult,
    ListResourcesResult,
    ListResourceTemplatesResult,
    PromptMessage,
    ReadResourceResult,
    TextContent,
)

from agents.mcp import MCPServer
from agents.mcp.server import _UNSET, _MCPServerWithClientSession, _UnsetType
from agents.mcp.util import MCPToolCustomDataExtractor, MCPToolMetaResolver, ToolFilter
from agents.tool import ToolErrorFunction

tee = shutil.which("tee") or ""
assert tee, "tee not found"


# Added dummy stream classes for patching stdio_client to avoid real I/O during tests
class DummyStream:
    async def send(self, msg):
        pass

    async def receive(self):
        raise Exception("Dummy receive not implemented")


class DummyStreamsContextManager:
    async def __aenter__(self):
        return (DummyStream(), DummyStream())

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        pass


class _TestFilterServer(_MCPServerWithClientSession):
    """Minimal implementation of _MCPServerWithClientSession for testing tool filtering"""

    def __init__(self, tool_filter: ToolFilter, server_name: str):
        # Initialize parent class properly to avoid type errors
        super().__init__(
            cache_tools_list=False,
            client_session_timeout_seconds=None,
            tool_filter=tool_filter,
        )
        self._server_name: str = server_name
        # Override some attributes for test isolation
        self.session = None
        self._cleanup_lock = asyncio.Lock()

    def create_streams(self):
        raise NotImplementedError("Not needed for filtering tests")

    @property
    def name(self) -> str:
        return self._server_name


class FakeMCPServer(MCPServer):
    def __init__(
        self,
        tools: list[MCPTool] | None = None,
        tool_filter: ToolFilter = None,
        server_name: str = "fake_mcp_server",
        require_approval: object | None = None,
        failure_error_function: ToolErrorFunction | None | _UnsetType = _UNSET,
        tool_meta_resolver: MCPToolMetaResolver | None = None,
        custom_data_extractor: MCPToolCustomDataExtractor | None = None,
    ):
        super().__init__(
            use_structured_content=False,
            require_approval=require_approval,  # type: ignore[arg-type]
            failure_error_function=failure_error_function,
            tool_meta_resolver=tool_meta_resolver,
            custom_data_extractor=custom_data_extractor,
        )
        self.tools: list[MCPTool] = tools or []
        self.tool_calls: list[str] = []
        self.tool_results: list[str] = []
        self.tool_metas: list[dict[str, Any] | None] = []
        self.tool_filter = tool_filter
        self._server_name = server_name
        self._custom_content: list[Content] | None = None
        self._response_meta: dict[str, Any] | None = None

    def add_tool(self, name: str, input_schema: dict[str, Any]):
        self.tools.append(MCPTool(name=name, inputSchema=input_schema))

    async def connect(self):
        pass

    async def cleanup(self):
        pass

    async def list_tools(self, run_context=None, agent=None):
        tools = self.tools

        # Apply tool filtering using the REAL implementation
        if self.tool_filter is not None:
            # Use the real _MCPServerWithClientSession filtering logic
            filter_server = _TestFilterServer(self.tool_filter, self.name)
            tools = await filter_server._apply_tool_filter(tools, run_context, agent)

        return tools

    async def call_tool(
        self,
        tool_name: str,
        arguments: dict[str, Any] | None,
        meta: dict[str, Any] | None = None,
    ) -> CallToolResult:
        self.tool_calls.append(tool_name)
        self.tool_results.append(f"result_{tool_name}_{json.dumps(arguments)}")
        self.tool_metas.append(meta)

        # Allow testing custom content scenarios
        if self._custom_content is not None:
            return CallToolResult(content=self._custom_content)

        return CallToolResult(
            content=[TextContent(text=self.tool_results[-1], type="text")],
            _meta=self._response_meta,
        )

    async def list_prompts(self, run_context=None, agent=None) -> ListPromptsResult:
        """Return empty list of prompts for fake server"""
        return ListPromptsResult(prompts=[])

    async def get_prompt(
        self, name: str, arguments: dict[str, Any] | None = None
    ) -> GetPromptResult:
        """Return a simple prompt result for fake server"""
        content = f"Fake prompt content for {name}"
        message = PromptMessage(role="user", content=TextContent(type="text", text=content))
        return GetPromptResult(description=f"Fake prompt: {name}", messages=[message])

    async def list_resources(self, cursor: str | None = None) -> ListResourcesResult:
        """Return empty list of resources for fake server."""
        return ListResourcesResult(resources=[])

    async def list_resource_templates(
        self, cursor: str | None = None
    ) -> ListResourceTemplatesResult:
        """Return empty list of resource templates for fake server."""
        return ListResourceTemplatesResult(resourceTemplates=[])

    async def read_resource(self, uri: str) -> ReadResourceResult:
        """Return empty resource contents for fake server."""
        return ReadResourceResult(contents=[])

    @property
    def name(self) -> str:
        return self._server_name
