"""
Meta-tools: The agent-facing interface for MCP Bridge.

These are the only tools exposed to AI agents. They provide a high-level
interface for discovering, configuring, activating, and using MCP servers
without overloading the agent's context window.
"""

from __future__ import annotations

import json
import logging
from typing import Annotated, Any

from pydantic import Field

from mcp_bridge.instances.manager import InstanceManager
from mcp_bridge.metrics.collector import MetricsCollector
from mcp_bridge.registry.catalog import ServerCatalog

logger = logging.getLogger(__name__)

# Global references set during server initialization
_catalog: ServerCatalog | None = None
_instance_manager: InstanceManager | None = None
_metrics: MetricsCollector | None = None


def init_meta_tools(catalog: ServerCatalog, instance_manager: InstanceManager, metrics: MetricsCollector) -> None:
    """Initialize meta-tools with their dependencies."""
    global _catalog, _instance_manager, _metrics
    _catalog = catalog
    _instance_manager = instance_manager
    _metrics = metrics


def _get_session_id(context: Any) -> str:
    """Extract agent session ID from the MCP context."""
    # FastMCP provides session info through the context
    # For now, use a default session ID; will be enhanced with per-session tracking
    if hasattr(context, "session") and hasattr(context.session, "id"):
        return str(context.session.id)
    return "default-session"


# --- Meta Tool Functions ---
# These are registered with FastMCP's @mcp.tool decorator in server.py


async def discover_servers(
    category: Annotated[
        str | None,
        Field(
            description="Filter by category (e.g., 'browser-automation', 'data-analytics', "
            "'development', 'ai-ml', 'communication', 'utilities', 'custom'). Leave empty to see all categories."
        ),
    ] = None,
    search: Annotated[
        str | None,
        Field(description="Search servers by name or description keyword."),
    ] = None,
    tag: Annotated[
        str | None,
        Field(description="Filter by tag (e.g., 'browser', 'database', 'git')."),
    ] = None,
) -> str:
    """
    Discover available MCP servers in the bridge catalog.

    Browse by category, search by name/description, or filter by tag.
    Returns a list of available servers with their IDs, descriptions,
    tool counts, and configurable options.

    Args:
        category: Filter by category (e.g., 'browser-automation', 'data-analytics',
                  'development', 'ai-ml', 'communication', 'utilities', 'custom').
                  Leave empty to see all categories.
        search: Search servers by name or description keyword.
        tag: Filter by tag (e.g., 'browser', 'database', 'git').

    Returns:
        JSON with available servers and their details.
    """
    assert _catalog is not None

    if not category and not search and not tag:
        # Return categories overview
        categories = await _catalog.list_categories()
        servers = await _catalog.list_servers()
        return json.dumps(
            {
                "total_servers": len(servers),
                "categories": categories,
                "hint": "Use category, search, or tag parameters to filter servers. "
                "Then use configure_server to set up a server instance.",
            },
            indent=2,
        )

    servers = await _catalog.list_servers(category=category, tag=tag, search=search)

    result = []
    for server in servers:
        entry = {
            "id": server.id,
            "name": server.name,
            "description": server.description,
            "category": server.category.value,
            "tags": server.tags,
            "configurable_options": [
                {
                    "name": opt.name,
                    "type": opt.type,
                    "description": opt.description,
                    "default": opt.default,
                    "required": opt.required,
                }
                for opt in server.configurable_options
            ],
        }
        result.append(entry)

    return json.dumps(
        {
            "servers": result,
            "count": len(result),
            "hint": "Use configure_server with a server ID and optional config to create an instance.",
        },
        indent=2,
    )


async def configure_server(
    server_id: Annotated[
        str,
        Field(description="The server ID from discover_servers (e.g., 'playwright-mcp')."),
    ],
    config: Annotated[
        str | None,
        Field(
            description="Optional JSON string with configuration options for this instance. "
            "The available options depend on the server (see discover_servers). "
            "Example: '{\"headless\": true, \"browser\": \"chromium\"}'"
        ),
    ] = None,
    session_id: Annotated[
        str,
        Field(description="Agent session identifier (auto-detected if not provided)."),
    ] = "default-session",
) -> str:
    """
    Configure a new MCP server instance with specific options.

    Creates a configured but not yet started instance. Use activate_server
    to actually start it and discover its tools.

    Args:
        server_id: The server ID from discover_servers (e.g., 'playwright-mcp').
        config: Optional JSON string with configuration options for this instance.
                The available options depend on the server (see discover_servers).
                Example: '{"headless": true, "browser": "chromium"}'
        session_id: Agent session identifier (auto-detected if not provided).

    Returns:
        JSON with instance_id and configuration details.
    """
    assert _instance_manager is not None

    config_dict = {}
    if config:
        try:
            config_dict = json.loads(config) if isinstance(config, str) else config
        except json.JSONDecodeError:
            return json.dumps({"error": f"Invalid config JSON: {config}"})

    try:
        instance = await _instance_manager.configure_instance(
            server_id=server_id,
            agent_session_id=session_id,
            config=config_dict,
        )

        return json.dumps(
            {
                "instance_id": instance.instance_id,
                "server_id": instance.server_id,
                "state": instance.state.value,
                "config": instance.config,
                "hint": f"Instance configured. Use activate_server('{instance.instance_id}') to start it.",
            },
            indent=2,
        )

    except Exception as e:
        return json.dumps({"error": str(e)})


async def activate_server(
    instance_id: Annotated[str, Field(description="The instance ID from configure_server.")],
) -> str:
    """
    Start a configured MCP server instance and discover its tools.

    This actually launches the server process, connects to it, and returns
    the list of available tools you can use via call_tool.

    Args:
        instance_id: The instance ID from configure_server.

    Returns:
        JSON with instance status and available tools.
    """
    assert _instance_manager is not None

    try:
        instance = await _instance_manager.activate_instance(instance_id)

        tools = [
            {
                "name": t.name,
                "description": t.description,
                "input_schema": t.input_schema,
            }
            for t in instance.tools
        ]

        return json.dumps(
            {
                "instance_id": instance.instance_id,
                "server_id": instance.server_id,
                "state": instance.state.value,
                "tools": tools,
                "tools_count": len(tools),
                "hint": f"Server active with {len(tools)} tools. "
                f"Use call_tool('{instance.instance_id}', '<tool_name>', '<args_json>') to invoke tools.",
            },
            indent=2,
        )

    except Exception as e:
        return json.dumps({"error": str(e)})


async def call_tool(
    instance_id: Annotated[str, Field(description="The instance ID of the active server.")],
    tool_name: Annotated[str, Field(description="Name of the tool to call (from activate_server's tools list).")],
    arguments: Annotated[
        str | None,
        Field(description="JSON string with tool arguments matching the tool's input schema."),
    ] = None,
    session_id: Annotated[
        str,
        Field(description="Agent session identifier (auto-detected if not provided)."),
    ] = "default-session",
) -> str:
    """
    Execute a tool on an active MCP server instance.

    Proxies the call to the actual MCP server and returns the result
    in the same format the tool would return natively.

    Args:
        instance_id: The instance ID of the active server.
        tool_name: Name of the tool to call (from activate_server's tools list).
        arguments: JSON string with tool arguments matching the tool's input schema.
        session_id: Agent session identifier (auto-detected if not provided).

    Returns:
        The tool's response, exactly as the native MCP server would return it.
    """
    assert _instance_manager is not None

    args_dict = {}
    if arguments:
        try:
            args_dict = json.loads(arguments) if isinstance(arguments, str) else arguments
        except json.JSONDecodeError:
            return json.dumps({"error": f"Invalid arguments JSON: {arguments}"})

    result = await _instance_manager.call_tool(
        instance_id=instance_id,
        tool_name=tool_name,
        arguments=args_dict,
        agent_session_id=session_id,
    )

    if result.success:
        # Return the raw result as the tool would natively
        if isinstance(result.result, str):
            return result.result
        return json.dumps(result.result, indent=2, default=str)
    else:
        return json.dumps(
            {
                "error": result.error,
                "instance_id": result.instance_id,
                "tool_name": result.tool_name,
            }
        )


async def deactivate_server(
    instance_id: Annotated[str, Field(description="The instance ID to deactivate.")],
) -> str:
    """
    Stop an active MCP server instance and free its resources.

    The server process is terminated and all its tools are removed from
    your available tools list. Call this when you're done with a server.

    Args:
        instance_id: The instance ID to deactivate.

    Returns:
        JSON confirming the deactivation.
    """
    assert _instance_manager is not None

    success = await _instance_manager.deactivate_instance(instance_id)

    if success:
        return json.dumps(
            {
                "instance_id": instance_id,
                "state": "stopped",
                "message": "Server instance stopped and resources freed.",
            }
        )
    else:
        return json.dumps({"error": f"Instance '{instance_id}' not found or already stopped."})


async def get_server_status(
    instance_id: Annotated[
        str | None,
        Field(description="Optional specific instance to check."),
    ] = None,
    session_id: Annotated[
        str,
        Field(description="Agent session identifier (auto-detected if not provided)."),
    ] = "default-session",
) -> str:
    """
    Get the status of server instances.

    Without instance_id, returns status of all your active instances.
    With instance_id, returns detailed status of that specific instance.

    Args:
        instance_id: Optional specific instance to check.
        session_id: Agent session identifier (auto-detected if not provided).

    Returns:
        JSON with instance status details.
    """
    assert _instance_manager is not None

    if instance_id:
        instance = _instance_manager.get_instance(instance_id)
        if not instance:
            return json.dumps({"error": f"Instance '{instance_id}' not found."})

        return json.dumps(
            {
                "instance_id": instance.instance_id,
                "server_id": instance.server_id,
                "state": instance.state.value,
                "tools_count": len(instance.tools),
                "total_tool_calls": instance.total_tool_calls,
                "total_errors": instance.total_errors,
                "started_at": instance.started_at.isoformat() if instance.started_at else None,
                "last_activity": instance.last_activity_at.isoformat() if instance.last_activity_at else None,
                "pid": instance.pid,
            },
            indent=2,
        )

    # All instances for this session
    instances = _instance_manager.get_instances_for_agent(session_id)
    result = []
    for inst in instances:
        result.append(
            {
                "instance_id": inst.instance_id,
                "server_id": inst.server_id,
                "state": inst.state.value,
                "tools_count": len(inst.tools),
                "total_tool_calls": inst.total_tool_calls,
            }
        )

    return json.dumps(
        {
            "active_instances": result,
            "count": len(result),
        },
        indent=2,
    )


async def list_active_tools(
    instance_id: Annotated[
        str | None,
        Field(description="Optional - list tools for a specific instance only."),
    ] = None,
    session_id: Annotated[
        str,
        Field(description="Agent session identifier (auto-detected if not provided)."),
    ] = "default-session",
) -> str:
    """
    List all tools available on your active server instances.

    Shows tools grouped by instance, with their names, descriptions,
    and input schemas. Use this to remind yourself what tools are available.

    Args:
        instance_id: Optional - list tools for a specific instance only.
        session_id: Agent session identifier (auto-detected if not provided).

    Returns:
        JSON with tools grouped by instance.
    """
    assert _instance_manager is not None

    if instance_id:
        tools = _instance_manager.get_instance_tools(instance_id)
        return json.dumps(
            {
                "instance_id": instance_id,
                "tools": [
                    {"name": t.name, "description": t.description, "input_schema": t.input_schema} for t in tools
                ],
                "count": len(tools),
            },
            indent=2,
        )

    # All tools across all instances for this agent
    instances = _instance_manager.get_instances_for_agent(session_id)
    all_tools: dict[str, list] = {}

    for inst in instances:
        if inst.state.value == "active":
            all_tools[inst.instance_id] = [
                {"name": t.name, "description": t.description, "input_schema": t.input_schema} for t in inst.tools
            ]

    total_count = sum(len(tools) for tools in all_tools.values())
    return json.dumps(
        {
            "tools_by_instance": all_tools,
            "total_tools": total_count,
            "active_instances": len(all_tools),
        },
        indent=2,
    )


async def parallel_call(
    calls_json: Annotated[
        str,
        Field(
            description="JSON array of call objects, each with: instance_id (target server), "
            "tool_name (tool to call), arguments (optional dict). "
            "Example: '[{\"instance_id\": \"abc\", \"tool_name\": \"navigate\", \"arguments\": {\"url\": \"...\"}}]'"
        ),
    ],
    session_id: Annotated[
        str,
        Field(description="Agent session identifier (auto-detected if not provided)."),
    ] = "default-session",
) -> str:
    """
    Execute multiple tool calls in parallel across different server instances.

    Send multiple calls at once and get all results back together.
    Each call targets a specific instance and tool.

    Args:
        calls_json: JSON array of call objects, each with:
            - instance_id: Target server instance
            - tool_name: Tool to call
            - arguments: (optional) Tool arguments as a dict
            Example: '[{"instance_id": "abc", "tool_name": "navigate", "arguments": {"url": "..."}}]'
        session_id: Agent session identifier (auto-detected if not provided).

    Returns:
        JSON array of results, one per call, in the same order.
    """
    assert _instance_manager is not None

    try:
        calls = json.loads(calls_json) if isinstance(calls_json, str) else calls_json
    except json.JSONDecodeError:
        return json.dumps({"error": f"Invalid calls JSON: {calls_json}"})

    if not isinstance(calls, list) or len(calls) == 0:
        return json.dumps({"error": "calls_json must be a non-empty JSON array"})

    results = await _instance_manager.parallel_call(calls, agent_session_id=session_id)

    return json.dumps(
        {
            "results": [
                {
                    "instance_id": r.instance_id,
                    "tool_name": r.tool_name,
                    "success": r.success,
                    "result": r.result if r.success else None,
                    "error": r.error if not r.success else None,
                    "latency_ms": r.latency_ms,
                }
                for r in results
            ],
            "total": len(results),
            "successful": sum(1 for r in results if r.success),
            "failed": sum(1 for r in results if not r.success),
        },
        indent=2,
    )


async def import_mcp_config(
    config_json: Annotated[
        str,
        Field(
            description="JSON string in standard MCP config format: "
            '{"mcpServers": {"server-name": {"command": "...", "args": [...], "env": {...}}}}'
        ),
    ],
    source_name: Annotated[
        str | None,
        Field(description="Optional name for this import source."),
    ] = None,
) -> str:
    """
    Import MCP server configurations from a standard mcp.json format.

    Supports the standard MCP config format used by Claude Desktop, Cursor, etc.
    You can also provide a GitHub raw URL to fetch the config from.

    Args:
        config_json: JSON string in standard MCP config format:
            {"mcpServers": {"server-name": {"command": "...", "args": [...], "env": {...}}}}
        source_name: Optional name for this import source.

    Returns:
        JSON with the list of imported servers.
    """
    assert _catalog is not None

    try:
        config = json.loads(config_json) if isinstance(config_json, str) else config_json
    except json.JSONDecodeError:
        return json.dumps({"error": "Invalid config JSON"})

    source = source_name or "user-import"

    try:
        imported = await _catalog.import_from_mcp_json(config, source=source)

        return json.dumps(
            {
                "imported_servers": [{"id": s.id, "name": s.name, "command": s.command} for s in imported],
                "count": len(imported),
                "message": f"Successfully imported {len(imported)} server(s).",
            },
            indent=2,
        )

    except Exception as e:
        return json.dumps({"error": str(e)})
