# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
MCP Tool Bridge

Bridges MCP tools to Familiar's tool system, enabling seamless integration
with the existing ToolRegistry and Agent.
"""

import asyncio
import logging
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional

from .client import MCPTool, MCPToolError, MCPToolResult
from .manager import MCPManager

if TYPE_CHECKING:
    from ..tools import Tool, ToolRegistry
    from .config import (
        MCPConfig,  # noqa: F401 - used as forward reference in setup_mcp_integration()
    )

logger = logging.getLogger(__name__)


def mcp_tool_to_familiar(
    mcp_tool: MCPTool,
    call_handler: Callable[[str, Dict[str, Any]], MCPToolResult],
    server_name: str = "",
    requires_confirmation: bool = False,
) -> "Tool":
    """
    Convert an MCP tool to an Familiar Tool.

    Args:
        mcp_tool: The MCP tool definition
        call_handler: Async function to call the tool
        server_name: Name of the server (for categorization)
        requires_confirmation: Whether tool requires user confirmation

    Returns:
        Familiar Tool instance
    """
    # Import here to avoid circular imports
    from ..tools import Tool

    def sync_handler(input_data: Dict[str, Any]) -> str:
        """Synchronous wrapper for async MCP tool call."""
        try:
            # Get or create event loop
            try:
                loop = asyncio.get_running_loop()
            except RuntimeError:
                loop = asyncio.new_event_loop()
                asyncio.set_event_loop(loop)

            # Call the async handler
            if loop.is_running():
                # We're in an async context, need to handle differently
                future = asyncio.ensure_future(call_handler(mcp_tool.name, input_data))
                # This is tricky - we need to wait synchronously
                # In practice, Familiar should use async tool calls
                result = asyncio.get_event_loop().run_until_complete(future)
            else:
                result = loop.run_until_complete(call_handler(mcp_tool.name, input_data))

            # Format result
            if result.is_error:
                return f"Error: {result.text}"
            return result.text or "(no output)"

        except MCPToolError as e:
            return f"MCP Tool Error: {e}"
        except Exception as e:
            logger.error(f"Error calling MCP tool {mcp_tool.name}: {e}")
            return f"Error: {str(e)}"

    return Tool(
        name=mcp_tool.name,
        description=mcp_tool.description,
        input_schema=_convert_input_schema(mcp_tool.input_schema),
        handler=sync_handler,
        category=f"mcp:{server_name}" if server_name else "mcp",
        requires_confirmation=requires_confirmation,
    )


def _convert_input_schema(mcp_schema: Dict[str, Any]) -> Dict[str, Any]:
    """
    Convert MCP input schema to Familiar's expected format.

    MCP uses standard JSON Schema, which should be compatible,
    but we ensure proper structure.
    """
    # MCP schemas are JSON Schema, which Familiar uses too
    # Just ensure we have required fields
    schema = mcp_schema.copy() if mcp_schema else {}

    if "type" not in schema:
        schema["type"] = "object"

    if "properties" not in schema:
        schema["properties"] = {}

    return schema


class MCPToolBridge:
    """
    Bridge between MCPManager and Familiar's ToolRegistry.

    Automatically syncs MCP tools to the registry and handles
    tool calls through the MCP manager.

    Usage:
        manager = MCPManager(config)
        registry = ToolRegistry()

        bridge = MCPToolBridge(manager, registry)
        await bridge.sync_tools()

        # Now registry has MCP tools available
        tools = registry.get_tools()
    """

    def __init__(
        self,
        manager: MCPManager,
        registry: Optional["ToolRegistry"] = None,
    ):
        self.manager = manager
        self._registry = registry
        self._mcp_tools: Dict[str, "Tool"] = {}  # full_name -> Tool

        # Register for tool change notifications
        manager.on_tools_changed(self._on_tools_changed)

    @property
    def registry(self) -> Optional["ToolRegistry"]:
        return self._registry

    @registry.setter
    def registry(self, value: "ToolRegistry") -> None:
        self._registry = value
        # Sync existing tools to new registry
        if self._mcp_tools and value:
            for tool in self._mcp_tools.values():
                value.register(tool)

    async def sync_tools(self) -> int:
        """
        Sync all MCP tools to the registry.

        Returns:
            Number of tools synced
        """
        if not self._registry:
            logger.warning("No registry set, tools not synced")
            return 0

        # Get all tools from all connected servers
        mcp_tools = await self.manager.list_all_tools()

        # Convert and register each tool
        new_count = 0
        for mcp_tool in mcp_tools:
            if mcp_tool.name not in self._mcp_tools:
                tool = self._create_familiar_tool(mcp_tool)
                self._mcp_tools[mcp_tool.name] = tool
                self._registry.register(tool)
                new_count += 1

        logger.info(f"Synced {new_count} new MCP tools (total: {len(self._mcp_tools)})")
        return new_count

    def _create_familiar_tool(self, mcp_tool: MCPTool) -> "Tool":
        """Create an Familiar tool from an MCP tool."""
        # Determine if confirmation is required
        requires_confirmation = False

        # Check server config
        sep = self.manager.config.tool_prefix_separator
        if sep in mcp_tool.name:
            server_name = mcp_tool.name.split(sep, 1)[0]
            server_state = self.manager._servers.get(server_name)
            if server_state:
                requires_confirmation = server_state.config.requires_confirmation

                # Also check trust level
                if (
                    server_state.config.trust_level == "untrusted"
                    and self.manager.config.require_confirmation_untrusted
                ):
                    requires_confirmation = True

        async def call_handler(tool_name: str, args: Dict[str, Any]) -> MCPToolResult:
            return await self.manager.call_tool(mcp_tool.name, args)

        return mcp_tool_to_familiar(
            mcp_tool,
            call_handler,
            server_name=server_name if sep in mcp_tool.name else "",
            requires_confirmation=requires_confirmation,
        )

    def _on_tools_changed(self, server_name: str, tools: List[MCPTool]) -> None:
        """Handle tools changed notification from a server."""
        if not self._registry:
            return

        sep = self.manager.config.tool_prefix_separator
        prefix = f"{server_name}{sep}"

        # Remove old tools from this server
        to_remove = [name for name in self._mcp_tools if name.startswith(prefix)]
        for name in to_remove:
            del self._mcp_tools[name]
            # Note: ToolRegistry doesn't have unregister, so old tools may persist

        # Add new tools
        for tool in tools:
            full_name = f"{prefix}{tool.name}"
            mcp_tool = MCPTool(
                name=full_name,
                description=f"[{server_name}] {tool.description}",
                input_schema=tool.input_schema,
                annotations=tool.annotations,
            )
            familiar_tool = self._create_familiar_tool(mcp_tool)
            self._mcp_tools[full_name] = familiar_tool
            self._registry.register(familiar_tool)

        logger.info(f"Updated tools from server '{server_name}': {len(tools)} tools")

    def get_mcp_tools(self) -> List["Tool"]:
        """Get all MCP tools as Familiar Tool instances."""
        return list(self._mcp_tools.values())

    def is_mcp_tool(self, tool_name: str) -> bool:
        """Check if a tool name is an MCP tool."""
        return tool_name in self._mcp_tools

    async def call_mcp_tool(
        self,
        tool_name: str,
        arguments: Dict[str, Any],
    ) -> str:
        """
        Call an MCP tool directly.

        This is an async alternative to using the synchronous handler.

        Args:
            tool_name: Full tool name (server:tool)
            arguments: Tool arguments

        Returns:
            Result as string
        """
        result = await self.manager.call_tool(tool_name, arguments)

        if result.is_error:
            return f"Error: {result.text}"
        return result.text or "(no output)"


class AsyncMCPToolRegistry:
    """
    Async-first tool registry for MCP tools.

    Use this when your agent supports async tool execution natively.
    """

    def __init__(self, manager: MCPManager):
        self.manager = manager
        self._tools_cache: Optional[List[MCPTool]] = None

    async def get_tools(self, force_refresh: bool = False) -> List[MCPTool]:
        """Get all available MCP tools."""
        if self._tools_cache is None or force_refresh:
            self._tools_cache = await self.manager.list_all_tools()
        return self._tools_cache

    async def call_tool(
        self,
        name: str,
        arguments: Optional[Dict[str, Any]] = None,
    ) -> MCPToolResult:
        """Call a tool by name."""
        return await self.manager.call_tool(name, arguments)

    def invalidate_cache(self) -> None:
        """Invalidate the tools cache."""
        self._tools_cache = None


# Convenience function for quick setup
async def setup_mcp_integration(
    config: "MCPConfig",
    registry: Optional["ToolRegistry"] = None,
) -> tuple[MCPManager, MCPToolBridge]:
    """
    Quick setup for MCP integration.

    Args:
        config: MCP configuration
        registry: Optional existing ToolRegistry to integrate with

    Returns:
        Tuple of (MCPManager, MCPToolBridge)

    Example:
        config = MCPConfig.from_yaml("~/.familiar/mcp.yaml")
        manager, bridge = await setup_mcp_integration(config, my_registry)
    """
    manager = MCPManager(config)
    await manager.connect_all()

    bridge = MCPToolBridge(manager, registry)
    await bridge.sync_tools()

    return manager, bridge
