"""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. Follow these steps:

1. **Discover the project layout**
   - Call `get_project_info` to obtain the `project_path`.
   - Inside that path, find the Python package directory — the subdirectory that
     contains an `__init__.py` (typically the project name with underscores,
     e.g. `my_project/`).

2. **[OPTIONAL] Explore samples from the registry**
   - Use `list_samples` to find sample projects with similar components.
   - Use `get_sample_info` to explore how these samples are implemented and structured.

2. **Create a dedicated file for each component**
   - Create a separate Python file for each component, named after the component
     (e.g. `{package_dir}/mzi_filter.py` for a component called `mzi_filter`).
   - At the top of each file, add a docstring or comment explaining that the
     component was designed with AI using the GDSFactory+ MCP server. Example:
     ```python
     \"\"\"MZI filter component — designed with AI using the GDSFactory+ MCP server.\"\"\"
     ```
   - The file MUST live inside the Python package directory (the one with
     `__init__.py`). Do NOT place it in a standalone directory —
     that is not a Python package and GDSFactory+ will not import it.
   - DO NOT modify `cells.py` — that file is reserved for PDK-defined cells.
   - Every cell function MUST use the `@gf.cell` decorator.

3. **CRITICAL — Register in `__init__.py`**
   - Open the package's `__init__.py` and add an import for the new module:
     `from .mzi_filter import *`  (matching the file you created)
   - Without this import, GDSFactory+ will not discover the new cells.
     The server only loads cells that are importable from the package.
     This is the #1 cause of "cell not found" issues.

4. **Build and verify**
   - Use `build_cells` to build the component.
   - Use `list_cells` / `get_cell_info` to confirm the cell is available.

5. **DRC / LVS / Connectivity checks**
    - paths for GDS files are always found in build/gds/ within the project directory.
    - Always run connectivity checks to verify the cell is properly connected.
    - For complex components, run DRC and LVS checks to ensure manufacturability.
    - When checks fail, analyze the reports, identify the issues, and iteratively fix the design.

Verification Check Guidelines (CRITICAL)
-----------------------------------------
**IMPORTANT**: Verification check failures (DRC, connectivity, LVS) are CRITICAL ERRORS
that MUST be fixed before fabrication. NEVER dismiss these errors as "expected" or "minor".

Understanding Check Results:
- **DRC (Design Rule Check)**: Validates that the layout meets fabrication constraints
  - FAILURES = Foundry will REJECT the design for manufacturing
  - Common issues: spacing violations, width violations, enclosure errors
  - ALL violations must be fixed — there are no "acceptable" DRC errors

- **Connectivity Check**: Verifies that all components are properly connected
  - FAILURES = Device will NOT function as intended
  - Common issues: disconnected ports, overlapping waveguides, open nets
  - ALL violations indicate broken connections that will cause device failure

- **LVS (Layout vs. Schematic)**: Ensures layout matches the intended circuit
  - FAILURES = Layout does NOT match the design intent
  - Common issues: wrong connectivity, missing/extra devices, pin mismatches
  - ALL violations mean the fabricated device will not match specifications

When Verification Checks Fail:
1. DO NOT proceed with fabrication or assume errors are minor
2. DO NOT dismiss errors as "expected behavior" — they almost never are
3. Carefully read the violation details and understand the root cause
4. Fix the design to eliminate ALL violations
5. Re-run checks to verify fixes worked
6. Only proceed when status = "PASSED" with 0 violations

The recommendations provided with check results are actionable guidance for fixing
critical design errors, not optional suggestions. Follow them to achieve a passing design.

Additional guidelines:
- Consult `list_samples` for examples of how to write cells.
- Always write designs in Python code, even if an example shows YAML.
- Before running DRC, ask the user — DRC checks can take a long time.
- Run DRC / LVS / Connectivity checks on custom cells you have built.
- When users ask for specific characteristics, run simulations and use Python
  scripts to analyze and verify the results.

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()
