"""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.
"""

from __future__ import annotations

import asyncio
import json
import logging
from typing import Any

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

from .client import FastAPIClient
from .config import MCPConfig
from .mappings import get_mapping, transform_request, transform_response
from .registry import get_registry_path
from .resources import get_all_resources, get_resource_content
from .tools import get_all_tools

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

logger = logging.getLogger(__name__)


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
    """
    # Create server instance
    server = Server("gdsfactoryplus")

    # Create HTTP client for FastAPI backend
    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)

        # Get resource content
        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]:  # noqa: PLR0911
        """Call an MCP tool.

        Args:
            name: Tool name
            arguments: Tool arguments

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

        # Handle special tools that don't require HTTP requests
        if name == "list_projects":
            try:
                projects = client.list_projects()
                return [
                    TextContent(
                        type="text",
                        text=json.dumps({"projects": projects}, indent=2),
                    )
                ]
            except Exception as e:
                error_msg = f"Failed to list projects: {e!s}"
                logger.exception(error_msg)
                return [TextContent(type="text", text=json.dumps({"error": error_msg}))]

        if name == "get_project_info":
            try:
                project = arguments.get("project")
                if not project:
                    return [
                        TextContent(
                            type="text",
                            text=json.dumps({"error": "project parameter is required"}),
                        )
                    ]
                info = await client.get_project_info(project)
                return [TextContent(type="text", text=json.dumps(info, indent=2))]
            except Exception as e:
                error_msg = f"Failed to get project info: {e!s}"
                logger.exception(error_msg)
                return [TextContent(type="text", text=json.dumps({"error": error_msg}))]

        # Get the endpoint mapping
        mapping = get_mapping(name)
        if mapping is None:
            error_msg = f"Unknown tool: {name}"
            logger.error(error_msg)
            return [TextContent(type="text", text=json.dumps({"error": error_msg}))]

        try:
            # Extract project parameter (optional)
            project = arguments.get("project")

            # Transform MCP arguments to HTTP request parameters
            transformed = transform_request(name, arguments)
            logger.debug("Transformed request: %s", transformed)

            # Extract request parameters
            method = mapping.method
            path = transformed.get("path", mapping.path)
            params = transformed.get("params")
            json_data = transformed.get("json_data")
            data = transformed.get("data")

            # Make HTTP request to FastAPI backend (with optional project routing)
            response = await client.request(
                method=method,
                path=path,
                params=params,
                json_data=json_data,
                data=data,
                project=project,
            )

            # Transform response to MCP format
            result = transform_response(name, response)
            logger.debug("Tool result: %s", result)

            # Return as text content
            return [
                TextContent(
                    type="text",
                    text=json.dumps(result, indent=2),
                )
            ]

        except Exception as e:
            error_msg = f"Tool execution failed: {e!s}"
            logger.exception(error_msg)
            return [
                TextContent(
                    type="text",
                    text=json.dumps({"error": error_msg}),
                )
            ]

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

    return server


def _log_server_discovery(client: FastAPIClient) -> None:
    """Log discovered GDSFactory+ servers at startup."""
    try:
        projects = client.list_projects()
        if projects:
            logger.info("Discovered %d GDSFactory+ server(s):", len(projects))
            for project in projects[:3]:  # Show first 3
                logger.info(
                    "  - %s (port %d, PDK: %s)",
                    project["project_name"],
                    project["port"],
                    project.get("pdk", "unknown"),
                )
            if len(projects) > 3:
                logger.info("  ... and %d more", len(projects) - 3)
        else:
            logger.warning(
                "No GDSFactory+ servers found. Start a server with: "
                "gfp serve --port 8787"
            )
            logger.info("Registry location: %s", get_registry_path())
    except Exception as e:
        logger.warning("Could not read server registry: %s", e)
        logger.info(
            "If GDSFactory+ is running, ensure registry is accessible at: %s",
            get_registry_path(),
        )


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)
    """
    # Configure logging
    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")
    logger.info("FastAPI base URL: %s", MCPConfig.get_api_url(api_url))
    logger.info("Timeout: %ds", MCPConfig.get_timeout())

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

    try:
        # Start HTTP client
        await client.start()

        # Discover available servers
        _log_server_discovery(client)

        # Run server with STDIO transport
        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:
        # Cleanup
        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()
