"""
MCP Bridge Server - Main entry point.

A dynamic MCP server bridge that exposes meta-tools for discovering,
configuring, and using multiple MCP servers on demand without overloading
the agent's context window.
"""

from __future__ import annotations

import asyncio
import logging
import sys

from fastmcp import FastMCP

from mcp_bridge.config.settings import get_settings
from mcp_bridge.credentials.store import create_default_store
from mcp_bridge.instances.manager import InstanceManager
from mcp_bridge.meta_tools import (
    activate_server,
    call_tool,
    configure_server,
    deactivate_server,
    discover_servers,
    get_server_status,
    import_mcp_config,
    init_meta_tools,
    list_active_tools,
    parallel_call,
)
from mcp_bridge.metrics.collector import MetricsCollector
from mcp_bridge.openapi.tools import (
    call_api_tool,
    configure_api_auth,
    customize_api_tool,
    get_api_tool_schema,
    import_openapi_spec,
    list_api_tools,
)
from mcp_bridge.platforms.platform_tools import (
    connect_platform,
    disconnect_platform,
    execute_workflow,
    get_platform_status,
    get_workflow_details,
    init_platform_tools,
    list_workflows,
)
from mcp_bridge.registry.catalog import ServerCatalog

logger = logging.getLogger(__name__)


def create_server() -> FastMCP:
    """Create and configure the MCP Bridge server with all meta-tools."""
    settings = get_settings()

    mcp = FastMCP(
        name=settings.server_name,
        instructions="""
MCP Grid is a meta-MCP server that manages multiple MCP servers on demand.

== LOCAL MCP SERVERS (9 tools) ==

1. discover_servers() - Browse available servers by category, search, or tag
2. configure_server() - Set up a server instance with specific options
3. activate_server() - Start the server and get its available tools
4. call_tool() - Execute a tool on an active server instance
5. list_active_tools() - See what tools are available on active instances
6. parallel_call() - Execute multiple tool calls across instances at once
7. get_server_status() - Check status of your server instances
8. deactivate_server() - Stop a server when you're done
9. import_mcp_config() - Import server configs from mcp.json format

== OPENAPI-TO-MCP ADAPTER (6 tools) ==

10. import_openapi_spec() - Import any OpenAPI/Swagger spec (URL, file, or string)
11. list_api_tools() - Browse all discovered API endpoints as tools
12. configure_api_auth() - Set API credentials (API key, Bearer, Basic)
13. customize_api_tool() - Rename or re-describe auto-generated tools
14. call_api_tool() - Execute an API endpoint as an MCP tool call
15. get_api_tool_schema() - Get full input schema for an API tool

== ENTERPRISE PLATFORM INTEGRATION (6 tools) ==

16. connect_platform() - Connect to an enterprise AI workflow platform
17. list_workflows() - Browse available workflow chains
18. get_workflow_details() - Inspect a chain's components and input variables
19. execute_workflow() - Run a workflow chain and get full results
20. get_platform_status() - Check connection and token status
21. disconnect_platform() - Disconnect and clear tokens from memory

WORKFLOW (Local Servers):
  discover → configure → activate → call_tool → deactivate

WORKFLOW (REST APIs via OpenAPI):
  import_openapi_spec → list_api_tools → configure_api_auth → call_api_tool

WORKFLOW (Enterprise Platforms):
  connect_platform → list_workflows → get_workflow_details
  → execute_workflow → disconnect_platform
""",
    )

    # Register meta-tools with FastMCP
    mcp.tool(
        name="discover_servers",
        description="Browse available MCP servers by category, search, or tag. "
        "Returns server IDs, descriptions, and configurable options.",
    )(discover_servers)

    mcp.tool(
        name="configure_server",
        description="Configure a new MCP server instance with specific options. "
        "Returns an instance_id to use with activate_server.",
    )(configure_server)

    mcp.tool(
        name="activate_server",
        description="Start a configured MCP server instance. "
        "Launches the server, discovers tools, and returns the available tools list.",
    )(activate_server)

    mcp.tool(
        name="call_tool",
        description="Execute a tool on an active MCP server instance. "
        "Proxies the call and returns results in the native format.",
    )(call_tool)

    mcp.tool(
        name="deactivate_server",
        description="Stop an active MCP server instance and free its resources.",
    )(deactivate_server)

    mcp.tool(
        name="get_server_status",
        description="Get status of your server instances - state, tool counts, call statistics, and resource usage.",
    )(get_server_status)

    mcp.tool(
        name="list_active_tools",
        description="List all tools available on your active server instances, grouped by instance with full schemas.",
    )(list_active_tools)

    mcp.tool(
        name="parallel_call",
        description="Execute multiple tool calls in parallel across different "
        "server instances. Send once, get all results together.",
    )(parallel_call)

    mcp.tool(
        name="import_mcp_config",
        description="Import MCP server configurations from standard mcp.json format. "
        "Supports Claude Desktop, Cursor, and similar config files.",
    )(import_mcp_config)

    # --- Enterprise Platform Integration Tools ---

    mcp.tool(
        name="connect_platform",
        description="Connect to an enterprise AI workflow platform with a JWT token. "
        "Enables browsing and executing workflow chains from the IDE.",
    )(connect_platform)

    mcp.tool(
        name="list_workflows",
        description="List available workflow chains on a connected enterprise platform. "
        "Search by title or filter to your own workflows.",
    )(list_workflows)

    mcp.tool(
        name="get_workflow_details",
        description="Get the full definition of a workflow chain including components, "
        "input variables, and required configuration parameters.",
    )(get_workflow_details)

    mcp.tool(
        name="execute_workflow",
        description="Execute an enterprise workflow chain via WebSocket. "
        "Returns the complete answer with tool call traces and execution stats.",
    )(execute_workflow)

    mcp.tool(
        name="get_platform_status",
        description="Check connection status and token validity for enterprise platforms.",
    )(get_platform_status)

    mcp.tool(
        name="disconnect_platform",
        description="Disconnect from an enterprise platform and clear all tokens from memory.",
    )(disconnect_platform)

    # --- OpenAPI-to-MCP Adapter Tools ---

    mcp.tool(
        name="import_openapi_spec",
        description="Import an OpenAPI/Swagger spec from URL, file, or raw JSON/YAML. "
        "Auto-discovers all endpoints and creates MCP tools from them.",
    )(import_openapi_spec)

    mcp.tool(
        name="list_api_tools",
        description="List all API operations from an imported OpenAPI spec. "
        "Browse by tag, search by name, and see parameter summaries.",
    )(list_api_tools)

    mcp.tool(
        name="configure_api_auth",
        description="Set authentication credentials for an imported API. "
        "Supports API key, Bearer token, Basic auth. Credentials are memory-only.",
    )(configure_api_auth)

    mcp.tool(
        name="customize_api_tool",
        description="Rename, re-describe, or enable/disable an API tool. "
        "Customize auto-generated names and descriptions.",
    )(customize_api_tool)

    mcp.tool(
        name="call_api_tool",
        description="Execute an API operation as an MCP tool call. "
        "Params auto-route to path, query, header, or body per the spec.",
    )(call_api_tool)

    mcp.tool(
        name="get_api_tool_schema",
        description="Get the full JSON Schema for an API tool's input parameters.",
    )(get_api_tool_schema)

    return mcp


async def initialize_services() -> tuple[ServerCatalog, InstanceManager, MetricsCollector]:
    """Initialize all backend services."""
    settings = get_settings()

    # Initialize catalog
    catalog = ServerCatalog()
    await catalog.initialize()

    # Initialize metrics
    metrics = MetricsCollector()
    await metrics.initialize()

    # Initialize credential store
    credentials = create_default_store()

    # Initialize instance manager
    instance_manager = InstanceManager(
        catalog=catalog,
        metrics=metrics,
        credential_store=credentials,
    )
    await instance_manager.start()

    # Wire up meta-tools
    init_meta_tools(catalog, instance_manager, metrics)

    # Wire up platform tools
    init_platform_tools()

    server_count = await catalog.get_server_count()
    logger.info(
        "MCP Bridge initialized: %d servers in catalog, data at %s",
        server_count,
        settings.data_dir,
    )

    return catalog, instance_manager, metrics


async def shutdown_services(
    catalog: ServerCatalog,
    instance_manager: InstanceManager,
    metrics: MetricsCollector,
) -> None:
    """Gracefully shut down all services."""
    logger.info("Shutting down MCP Bridge...")
    await instance_manager.stop()
    await metrics.close()
    await catalog.close()
    logger.info("MCP Bridge shutdown complete")


async def _start_management_api(
    catalog: ServerCatalog,
    instance_manager: InstanceManager,
    metrics: MetricsCollector,
) -> None:
    """Start the management REST API for the VS Code / Cursor extension.

    Runs on a background asyncio task.  When Cursor respawns the MCP
    server process, the *old* process may still hold the port briefly.
    We retry a few times with backoff to let the old process die, then
    bind the port ourselves.  This NEVER crashes the MCP server.
    """
    import socket

    import uvicorn

    from mcp_bridge.api.management import create_management_app

    settings = get_settings()
    api_settings = settings.management_api

    if not api_settings.enabled:
        logger.info("Management API disabled by configuration")
        return

    port = api_settings.port
    host = api_settings.host

    # Retry up to 5 times (total ~15 seconds) to let the old process release the port
    max_retries = 5
    bound = False
    for attempt in range(max_retries):
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            sock.bind((host, port))
            sock.close()
            bound = True
            break
        except OSError:
            if attempt < max_retries - 1:
                wait = 1.5 * (attempt + 1)  # 1.5s, 3s, 4.5s, 6s
                logger.info(
                    "Management API port %d in use, retrying in %.1fs (%d/%d)...",
                    port, wait, attempt + 1, max_retries,
                )
                await asyncio.sleep(wait)
            else:
                logger.warning(
                    "Management API port %d still in use after %d retries. "
                    "Sidebar will not connect to this instance. MCP server continues.",
                    port, max_retries,
                )
                return

    if not bound:
        return

    app = create_management_app(catalog, instance_manager, metrics)

    config = uvicorn.Config(
        app,
        host=host,
        port=port,
        log_level="warning",
        access_log=False,
    )
    server = uvicorn.Server(config)

    logger.info("Management API starting on http://%s:%d", host, port)

    try:
        await server.serve()
    except Exception as exc:
        # NEVER let the management API crash the MCP server
        logger.warning("Management API stopped: %s (MCP server continues)", exc)


def main():
    """Main entry point for the MCP Bridge server.

    Starts both:
      1. The MCP server (stdio or HTTP) -- for agent tool access
      2. The management REST API (HTTP on port 19420) -- for the VS Code extension UI

    Both run in the same process sharing catalog, instances, and metrics.
    """
    settings = get_settings()

    # Configure logging
    logging.basicConfig(
        level=getattr(logging, settings.log_level.upper(), logging.INFO),
        format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
        stream=sys.stderr,
    )

    # Create server
    mcp = create_server()

    # Services will be initialized when the server starts
    catalog: ServerCatalog | None = None
    instance_manager: InstanceManager | None = None
    metrics_collector: MetricsCollector | None = None

    async def _run():
        nonlocal catalog, instance_manager, metrics_collector

        # Initialize services
        catalog, instance_manager, metrics_collector = await initialize_services()

        try:
            # Start management API as a background task
            # (provides the REST/WebSocket API for the extension sidebar)
            mgmt_task = asyncio.create_task(
                _start_management_api(catalog, instance_manager, metrics_collector)
            )

            # Run the MCP server in the foreground (agent talks to this)
            if settings.server_transport == "http":
                await mcp.run_async(transport="streamable-http", port=settings.server_port)
            else:
                await mcp.run_async(transport="stdio")
        finally:
            if catalog and instance_manager and metrics_collector:
                await shutdown_services(catalog, instance_manager, metrics_collector)

    try:
        asyncio.run(_run())
    except KeyboardInterrupt:
        logger.info("Interrupted by user")
    except Exception as e:
        logger.error("Fatal error: %s", e, exc_info=True)
        sys.exit(1)


if __name__ == "__main__":
    main()
