"""MCP Server generator for integrations (ISS-0014).

This module generates standalone MCP server wrappers from integration manifests,
enabling Claude to use integrations as tools directly.

Example:
    generator = MCPServerGenerator()

    # Generate server code
    code = generator.generate_server(manifest, manifest_path)

    # Write server to file
    server_path = generator.write_server(manifest, manifest_path, output_dir)

    # Register with .mcp.json
    generator.register_with_mcp_json("github", server_path, mcp_json_path)
"""

from __future__ import annotations

import json
from pathlib import Path  # noqa: TC003 - used at runtime
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from .manifest import IntegrationManifest


class MCPServerGenerator:
    """Generates MCP server wrappers from integration manifests.

    Creates standalone Python MCP servers that expose integration operations
    as tools for Claude to use directly.
    """

    SERVER_TEMPLATE = '''#!/usr/bin/env python3
"""Auto-generated MCP server for {name} integration.

This server exposes {name} API operations as MCP tools.
Generated by claude-mpm integrate.

Usage:
    python {server_filename}

Or via .mcp.json configuration.
"""

from __future__ import annotations

import asyncio
import json
import os
import sys
from pathlib import Path
from typing import Any

# Check for MCP package availability
try:
    from mcp.server import Server
    from mcp.server.stdio import stdio_server
    from mcp.types import TextContent, Tool

    MCP_AVAILABLE = True
except ImportError:
    MCP_AVAILABLE = False
    print(
        "MCP package not available. Install with: pip install mcp",
        file=sys.stderr,
    )
    sys.exit(1)

# Check for integration client availability
try:
    from claude_mpm.integrations.core.client import IntegrationClient
    from claude_mpm.integrations.core.manifest import IntegrationManifest

    CLIENT_AVAILABLE = True
except ImportError:
    CLIENT_AVAILABLE = False
    print(
        "claude-mpm package not available. Install with: pip install claude-mpm",
        file=sys.stderr,
    )
    sys.exit(1)

# Server configuration
MANIFEST_PATH = Path("{manifest_path}")
SERVER_NAME = "{name}-integration"

# Initialize MCP server
server = Server(SERVER_NAME)


def load_manifest() -> IntegrationManifest:
    """Load integration manifest from file."""
    return IntegrationManifest.from_yaml(MANIFEST_PATH)


def get_credentials() -> dict[str, str]:
    """Load credentials from environment variables.

    Searches for credentials in:
    1. Environment variables directly
    2. .env file in integration directory
    3. .env file in project root
    """
    credentials: dict[str, str] = {{}}
    manifest = load_manifest()

    for cred in manifest.auth.credentials:
        # Check environment variable
        value = os.environ.get(cred.name)
        if value:
            credentials[cred.name] = value
            continue

        # Check .env in integration directory
        env_path = MANIFEST_PATH.parent / ".env"
        if env_path.exists():
            with env_path.open() as f:
                for line in f:
                    line = line.strip()
                    if line.startswith(cred.name + "="):
                        credentials[cred.name] = line.split("=", 1)[1].strip('"').strip("'")
                        break

    return credentials


def build_input_schema(operation: Any) -> dict[str, Any]:
    """Build JSON Schema for operation parameters."""
    properties: dict[str, Any] = {{}}
    required: list[str] = []

    for param in operation.parameters:
        prop: dict[str, Any] = {{
            "description": param.description or f"Parameter {{param.name}}",
        }}

        # Map parameter types to JSON Schema types
        type_map = {{
            "string": "string",
            "int": "integer",
            "integer": "integer",
            "float": "number",
            "bool": "boolean",
            "boolean": "boolean",
            "file": "string",
        }}
        prop["type"] = type_map.get(param.type, "string")

        if param.default is not None:
            prop["default"] = param.default

        properties[param.name] = prop

        if param.required:
            required.append(param.name)

    schema: dict[str, Any] = {{
        "type": "object",
        "properties": properties,
    }}

    if required:
        schema["required"] = required

    return schema


@server.list_tools()
async def list_tools() -> list[Tool]:
    """List available tools from the integration."""
    manifest = load_manifest()
    tools: list[Tool] = []

    for op in manifest.operations:
        # Filter by MCP config if specified
        if manifest.mcp.tools is not None and op.name not in manifest.mcp.tools:
            continue

        tools.append(
            Tool(
                name=op.name,
                description=op.description,
                inputSchema=build_input_schema(op),
            )
        )

    return tools


@server.call_tool()
async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
    """Execute a tool/operation."""
    manifest = load_manifest()
    credentials = get_credentials()

    try:
        async with IntegrationClient(manifest, credentials) as client:
            result = await client.call_operation(name, **arguments)

        return [
            TextContent(
                type="text",
                text=json.dumps(result, indent=2, default=str),
            )
        ]
    except Exception as e:
        return [
            TextContent(
                type="text",
                text=json.dumps(
                    {{"error": str(e), "operation": name, "arguments": arguments}},
                    indent=2,
                ),
            )
        ]


async def main() -> None:
    """Run the MCP server."""
    async with stdio_server() as (read_stream, write_stream):
        await server.run(read_stream, write_stream, server.create_initialization_options())


if __name__ == "__main__":
    asyncio.run(main())
'''

    def generate_server(
        self,
        manifest: IntegrationManifest,
        manifest_path: Path,
    ) -> str:
        """Generate MCP server Python code.

        Args:
            manifest: Integration manifest.
            manifest_path: Path to the manifest file.

        Returns:
            Generated Python server code.
        """
        server_filename = f"{manifest.name}_mcp_server.py"

        return self.SERVER_TEMPLATE.format(
            name=manifest.name,
            manifest_path=str(manifest_path.resolve()),
            server_filename=server_filename,
        )

    def write_server(
        self,
        manifest: IntegrationManifest,
        manifest_path: Path,
        output_dir: Path,
    ) -> Path:
        """Write MCP server to file.

        Args:
            manifest: Integration manifest.
            manifest_path: Path to the manifest file.
            output_dir: Directory to write server file.

        Returns:
            Path to the generated server file.
        """
        server_code = self.generate_server(manifest, manifest_path)
        server_path = output_dir / f"{manifest.name}_mcp_server.py"

        output_dir.mkdir(parents=True, exist_ok=True)
        server_path.write_text(server_code, encoding="utf-8")

        # Make executable
        server_path.chmod(0o755)

        return server_path

    def register_with_mcp_json(
        self,
        name: str,
        server_path: Path,
        mcp_json_path: Path,
    ) -> bool:
        """Add integration server to .mcp.json.

        Args:
            name: Integration name.
            server_path: Path to the generated server file.
            mcp_json_path: Path to .mcp.json file.

        Returns:
            True if registration succeeded, False otherwise.
        """
        # Load existing config or create new
        if mcp_json_path.exists():
            with mcp_json_path.open() as f:
                config = json.load(f)
        else:
            config = {"mcpServers": {}}

        if "mcpServers" not in config:
            config["mcpServers"] = {}

        # Build server config
        server_name = f"{name}-integration"
        config["mcpServers"][server_name] = {
            "type": "stdio",
            "command": "python",
            "args": [str(server_path.resolve())],
            "env": {},
        }

        # Write updated config
        mcp_json_path.parent.mkdir(parents=True, exist_ok=True)
        with mcp_json_path.open("w") as f:
            json.dump(config, f, indent=2)
            f.write("\n")

        return True

    def unregister_from_mcp_json(
        self,
        name: str,
        mcp_json_path: Path,
    ) -> bool:
        """Remove integration server from .mcp.json.

        Args:
            name: Integration name.
            mcp_json_path: Path to .mcp.json file.

        Returns:
            True if unregistration succeeded, False otherwise.
        """
        if not mcp_json_path.exists():
            return False

        with mcp_json_path.open() as f:
            config = json.load(f)

        if "mcpServers" not in config:
            return False

        server_name = f"{name}-integration"
        if server_name not in config["mcpServers"]:
            return False

        del config["mcpServers"][server_name]

        with mcp_json_path.open("w") as f:
            json.dump(config, f, indent=2)
            f.write("\n")

        return True

    def get_server_path(
        self,
        manifest_name: str,
        integration_path: Path,
    ) -> Path:
        """Get the expected path for an MCP server file.

        Args:
            manifest_name: Integration name from manifest.
            integration_path: Path to integration directory.

        Returns:
            Expected path to MCP server file.
        """
        return integration_path / f"{manifest_name}_mcp_server.py"
