"""FastMCP run command implementation with enhanced type hints."""

import json
import re
import sys
from pathlib import Path
from typing import Any, Literal

from mcp.server.fastmcp import FastMCP as FastMCP1x

from fastmcp.server.server import FastMCP
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.mcp_server_config import (
    MCPServerConfig,
)
from fastmcp.utilities.mcp_server_config.v1.sources.filesystem import FileSystemSource

logger = get_logger("cli.run")

# Type aliases for better type safety
TransportType = Literal["stdio", "http", "sse", "streamable-http"]
LogLevelType = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]


def is_url(path: str) -> bool:
    """Check if a string is a URL."""
    url_pattern = re.compile(r"^https?://")
    return bool(url_pattern.match(path))


def create_client_server(url: str) -> Any:
    """Create a FastMCP server from a client URL.

    Args:
        url: The URL to connect to

    Returns:
        A FastMCP server instance
    """
    try:
        import fastmcp

        client = fastmcp.Client(url)
        server = fastmcp.FastMCP.as_proxy(client)
        return server
    except Exception as e:
        logger.error(f"Failed to create client for URL {url}: {e}")
        sys.exit(1)


def create_mcp_config_server(mcp_config_path: Path) -> FastMCP[None]:
    """Create a FastMCP server from a MCPConfig."""
    from fastmcp import FastMCP

    with mcp_config_path.open() as src:
        mcp_config = json.load(src)

    server = FastMCP.as_proxy(mcp_config)
    return server


def load_mcp_server_config(config_path: Path) -> MCPServerConfig:
    """Load a FastMCP configuration from a fastmcp.json file.

    Args:
        config_path: Path to fastmcp.json file

    Returns:
        MCPServerConfig object
    """
    config = MCPServerConfig.from_file(config_path)

    # Apply runtime settings from deployment config
    config.deployment.apply_runtime_settings(config_path)

    return config


async def run_command(
    server_spec: str,
    transport: TransportType | None = None,
    host: str | None = None,
    port: int | None = None,
    path: str | None = None,
    log_level: LogLevelType | None = None,
    server_args: list[str] | None = None,
    show_banner: bool = True,
    use_direct_import: bool = False,
    skip_source: bool = False,
) -> None:
    """Run a MCP server or connect to a remote one.

    Args:
        server_spec: Python file, object specification (file:obj), config file, or URL
        transport: Transport protocol to use
        host: Host to bind to when using http transport
        port: Port to bind to when using http transport
        path: Path to bind to when using http transport
        log_level: Log level
        server_args: Additional arguments to pass to the server
        show_banner: Whether to show the server banner
        use_direct_import: Whether to use direct import instead of subprocess
        skip_source: Whether to skip source preparation step
    """
    # Special case: URLs
    if is_url(server_spec):
        # Handle URL case
        server = create_client_server(server_spec)
        logger.debug(f"Created client proxy server for {server_spec}")
    # Special case: MCPConfig files (legacy)
    elif server_spec.endswith(".json"):
        # Load JSON and check which type of config it is
        config_path = Path(server_spec)
        with open(config_path) as f:
            data = json.load(f)

        # Check if it's an MCPConfig first (has canonical mcpServers key)
        if "mcpServers" in data:
            # It's an MCP config
            server = create_mcp_config_server(config_path)
        else:
            # It's a FastMCP config - load it properly
            config = load_mcp_server_config(config_path)

            # Merge deployment config with CLI arguments (CLI takes precedence)
            transport = transport or config.deployment.transport
            host = host or config.deployment.host
            port = port or config.deployment.port
            path = path or config.deployment.path
            log_level = log_level or config.deployment.log_level
            server_args = (
                server_args if server_args is not None else config.deployment.args
            )

            # Prepare source only (environment is handled by uv run)
            await config.prepare_source() if not skip_source else None

            # Load the server using the source
            from contextlib import nullcontext

            from fastmcp.cli.cli import with_argv

            # Use sys.argv context manager if deployment args specified
            argv_context = with_argv(server_args) if server_args else nullcontext()

            with argv_context:
                server = await config.source.load_server()

            logger.debug(f'Found server "{server.name}" from config {config_path}')
    else:
        # Regular file case - create a MCPServerConfig with FileSystemSource
        source = FileSystemSource(path=server_spec)
        config = MCPServerConfig(source=source)

        # Prepare source only (environment is handled by uv run)
        await config.prepare_source() if not skip_source else None

        # Load the server
        from contextlib import nullcontext

        from fastmcp.cli.cli import with_argv

        # Use sys.argv context manager if server_args specified
        argv_context = with_argv(server_args) if server_args else nullcontext()

        with argv_context:
            server = await config.source.load_server()

        logger.debug(f'Found server "{server.name}" in {source.path}')

    # Run the server

    # handle v1 servers
    if isinstance(server, FastMCP1x):
        run_v1_server(server, host=host, port=port, transport=transport)
        return

    kwargs = {}
    if transport:
        kwargs["transport"] = transport
    if host:
        kwargs["host"] = host
    if port:
        kwargs["port"] = port
    if path:
        kwargs["path"] = path
    if log_level:
        kwargs["log_level"] = log_level

    if not show_banner:
        kwargs["show_banner"] = False

    try:
        await server.run_async(**kwargs)
    except Exception as e:
        logger.error(f"Failed to run server: {e}")
        sys.exit(1)


def run_v1_server(
    server: FastMCP1x,
    host: str | None = None,
    port: int | None = None,
    transport: TransportType | None = None,
) -> None:
    from functools import partial

    if host:
        server.settings.host = host
    if port:
        server.settings.port = port
    match transport:
        case "stdio":
            runner = partial(server.run)
        case "http" | "streamable-http" | None:
            runner = partial(server.run, transport="streamable-http")
        case "sse":
            runner = partial(server.run, transport="sse")

    runner()
