"""MCP server implementation for GDSFactory+.

This module provides the main MCP server that exposes GDSFactory+ operations
as tools for AI assistants using the STDIO transport.

The server uses a handler-based architecture where each tool has its own
handler class that encapsulates:
- Tool definition (name, description, input schema)
- Request transformation (MCP args -> HTTP params/body)
- Response transformation (HTTP response -> MCP format)
- Execution logic (handle method)
"""

from __future__ import annotations

import asyncio
import logging
from typing import Any

from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import (
    ImageContent,
    Resource,
    TextContent,
    Tool,
)

from .client import FastAPIClient
from .config import MCPConfig
from .resources import get_all_resources, get_resource_content
from .tools import get_all_tools, get_handler

__all__ = ["create_server", "run_server", "main"]

logger = logging.getLogger(__name__)


_INSTRUCTIONS = """
GDSFactory+ MCP Server Instructions
===================================

This MCP server connects AI assistants to GDSFactory+ for photonic IC design.
Use this context to understand how to interact with the server effectively.

Server Discovery
----------------
Use the `list_projects` tool to discover running GDSFactory+ servers / projects.
Each project has its own server with a unique port.

If no servers are found, instruct the user to:
1. Open VSCode with the GDSFactory+ extension installed
2. Open a GDSFactory+ project folder
3. The extension will automatically start and register the server

Typically, users will invoke you from the project directory they are working on.
If unclear, ask the user what project they are working on.

Multi-Project Routing
---------------------
All tools (except list_projects) support an optional `project` parameter.
Use this to target a specific project when multiple GDSFactory+ servers are running.

If not specified, the first available server from the registry is used as a fallback.
You are adviced against ever using this fallback.

Designing Custom cells / factories
---------------------------------
Users may request you to build and design custom cells. Always consult the `list_samples` tool to discover examples of how to design cells.
Always make sure to run DRC/LVS/Connectivity checks on custom cells you have built.
Before running a DRC consult with the user if they would like to run the DRC, as these can take a long time to complete.
When users ask for specific characteristics of a cell / factory, you are to run simulations and use python scripts to analyze and verify the results.
Always write your new designs in python code, even though an example might show yaml.
Make sure to save your designs in project_name/custom_cells/**.py, and notify the user that they are saved here.

Best Practices
--------------
1. Always call `list_projects` first to discover available servers
2. Use the `project` parameter when working with multiple projects
3. For verification tools (DRC, connectivity, LVS), provide full paths to GDS files
4. Simulation results may be large - the server optimizes responses for token efficiency
5. When building cells, use the `visualize` parameter to get PNG previews
"""


def create_server(api_url: str | None = None) -> Server:
    """Create an MCP server instance.

    Args:
        api_url: Optional FastAPI base URL (default from config)

    Returns:
        Configured MCP Server instance
    """
    server = Server("gdsfactoryplus", instructions=_INSTRUCTIONS.strip())

    client = FastAPIClient(api_url)

    @server.list_tools()
    async def list_tools() -> list[Tool]:
        """List all available MCP tools.

        Returns:
            List of tool definitions
        """
        tools = get_all_tools()
        logger.info("Listing %d tools", len(tools))
        return tools

    @server.list_resources()
    async def list_resources() -> list[Resource]:
        """List all available MCP resources.

        Returns:
            List of resource definitions
        """
        resources = get_all_resources()
        logger.info("Listing %d resources", len(resources))
        return resources

    @server.read_resource()
    async def read_resource(uri: str) -> str:
        """Read a specific resource by URI.

        Args:
            uri: Resource URI

        Returns:
            Resource content as string

        Raises:
            ValueError: If resource URI is not found
        """
        logger.info("Resource requested: %s", uri)

        content = get_resource_content(uri)
        if content is None:
            error_msg = f"Unknown resource URI: {uri}"
            logger.error(error_msg)
            raise ValueError(error_msg)

        return content

    @server.call_tool()
    async def call_tool(
        name: str, arguments: dict[str, Any]
    ) -> list[TextContent | ImageContent]:
        """Call an MCP tool.

        Uses handler dispatch to route tool calls to the appropriate handler.
        Each handler encapsulates request/response transformation and execution.

        Args:
            name: Tool name
            arguments: Tool arguments

        Returns:
            List of text and/or image content responses
        """
        logger.info("Tool called: %s", name)
        logger.debug("Arguments: %s", arguments)

        handler = get_handler(name)
        if handler is None:
            import json

            error_msg = f"Unknown tool: {name}"
            logger.error(error_msg)
            return [TextContent(type="text", text=json.dumps({"error": error_msg}))]

        return await handler.handle(arguments, client)

    server._http_client = client  # type: ignore[attr-defined]  # noqa: SLF001

    return server


async def run_server(api_url: str | None = None) -> None:
    """Run the MCP server with STDIO transport.

    Args:
        api_url: Optional FastAPI base URL (default from config)
    """
    log_level = logging.DEBUG if MCPConfig.DEBUG else logging.INFO
    logging.basicConfig(
        level=log_level,
        format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    )

    logger.info("Starting GDSFactory+ MCP server")
    base_url_info = MCPConfig.get_api_url(api_url) or "registry-based routing"
    logger.info("Base URL: %s", base_url_info)
    logger.info("Timeout: %ds", MCPConfig.get_timeout())

    server = create_server(api_url)
    client: FastAPIClient = server._http_client  # type: ignore[attr-defined]  # noqa: SLF001

    try:
        await client.start()

        async with stdio_server() as (read_stream, write_stream):
            logger.info("MCP server ready (STDIO transport)")
            await server.run(
                read_stream,
                write_stream,
                server.create_initialization_options(),
            )

    except KeyboardInterrupt:
        logger.info("Shutting down MCP server (keyboard interrupt)")
    except Exception:
        logger.exception("MCP server error")
        raise
    finally:
        await client.close()
        logger.info("MCP server stopped")


def main(api_url: str | None = None) -> None:
    """Main entry point for MCP server.

    Args:
        api_url: Optional FastAPI base URL (default from config)
    """
    try:
        asyncio.run(run_server(api_url))
    except KeyboardInterrupt:
        pass
    except Exception:
        logger.exception("Fatal error")
        raise


if __name__ == "__main__":
    main()
