"""Remote MCP server using official MCP SDK with HTTP transport and Auth0 OAuth authentication.

This server implements RFC 9728 OAuth Protected Resource Metadata and uses the official
MCP Python SDK with StreamableHTTP transport.

AUTHENTICATION:
- Auth0 OAuth integration using JWT token validation via JWKS
- RFC 9728 Protected Resource Metadata endpoint at /.well-known/oauth-protected-resource
- Required scopes: permissions from Auth0 JWT token claims

CONFIGURATION:
Credentials can be loaded from either:
1. Environment variables (AUTH0_DOMAIN, AUTH0_CLIENT_ID, etc.)
2. infrastructure/auth0-credentials.json (created by auth0-setup.sh)

Usage:
    python -m point_topic_mcp.server_http
"""

import json
import logging
import os
from contextlib import asynccontextmanager
from pathlib import Path
from typing import AsyncIterator
from urllib.parse import quote

import httpx
from dotenv import load_dotenv
from mcp.server import Server
from mcp.types import TextContent, Tool, CallToolRequest
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
from starlette.routing import Route
from starlette.types import Receive, Scope, Send

from point_topic_mcp.tools import get_all_tools
from point_topic_mcp.auth.auth0_helpers import Auth0TokenVerifier

# Load environment variables
load_dotenv()

logger = logging.getLogger(__name__)

# Auth0 Configuration
AUTH0_DOMAIN = os.getenv("AUTH0_DOMAIN", "point-topic.eu.auth0.com")
AUTH0_AUDIENCE = os.getenv("AUTH0_AUDIENCE", "https://point-topic-mcp.fastmcp.app/")
AUTH0_CLIENT_ID = os.getenv("AUTH0_CLIENT_ID")
AUTH0_CLIENT_SECRET = os.getenv("AUTH0_CLIENT_SECRET")
MCP_BASE_URL = os.getenv("MCP_BASE_URL", "https://point-topic-mcp.fastmcp.app")

# JWKS URL for token validation
JWKS_URL = f"https://{AUTH0_DOMAIN}/.well-known/jwks.json"

# Global token verifier instance
_token_verifier: Auth0TokenVerifier | None = None


def load_auth0_credentials():
    """Load Auth0 credentials from file or environment variables."""
    # First try environment variables
    domain = os.getenv("AUTH0_DOMAIN")
    if domain and domain != "point-topic.eu.auth0.com":
        return {
            "domain": domain,
            "client_id": os.getenv("AUTH0_CLIENT_ID", ""),
            "client_secret": os.getenv("AUTH0_CLIENT_SECRET", ""),
            "audience": os.getenv("AUTH0_AUDIENCE", AUTH0_AUDIENCE),
            "base_url": os.getenv("MCP_BASE_URL", MCP_BASE_URL),
        }

    # Try credentials file
    creds_file = (
        Path(__file__).parent.parent.parent
        / "infrastructure"
        / "auth0-credentials.json"
    )
    if creds_file.exists():
        with open(creds_file) as f:
            creds = json.load(f)
        return {
            "domain": creds.get("auth0_domain", AUTH0_DOMAIN),
            "client_id": creds.get("client_id", AUTH0_CLIENT_ID or ""),
            "client_secret": creds.get("client_secret", AUTH0_CLIENT_SECRET or ""),
            "audience": creds.get("api_audience", AUTH0_AUDIENCE),
            "base_url": os.getenv("MCP_BASE_URL", MCP_BASE_URL),
        }

    # Return defaults
    return {
        "domain": AUTH0_DOMAIN,
        "client_id": AUTH0_CLIENT_ID or "",
        "client_secret": AUTH0_CLIENT_SECRET or "",
        "audience": AUTH0_AUDIENCE,
        "base_url": MCP_BASE_URL,
    }


async def oauth_metadata_endpoint(request: Request) -> JSONResponse:
    """RFC 9728 OAuth Protected Resource Metadata endpoint."""
    creds = load_auth0_credentials()
    return JSONResponse(
        content={
            "resource": creds["base_url"],
            "authorization_servers": [f"https://{creds['domain']}/"],
            "scopes_supported": [],
            "bearer_methods_supported": ["header"],
        }
    )


async def health_endpoint(request: Request) -> JSONResponse:
    """Health check endpoint."""
    return JSONResponse(content={"status": "healthy", "server": "Point Topic MCP"})


async def mcp_endpoint(request: Request) -> Response:
    """MCP endpoint - handles JSON-RPC requests with authentication."""
    global _token_verifier

    # Validate authentication
    auth_header = request.headers.get("Authorization", "")
    if not auth_header.startswith("Bearer "):
        # Return 401 with WWW-Authenticate header (RFC 9728 compliant)
        # The resource_metadata parameter tells clients where to find OAuth server info
        www_auth = (
            f"Bearer "
            f'resource_metadata="{MCP_BASE_URL}/.well-known/oauth-protected-resource"'
        )
        return JSONResponse(
            status_code=401,
            headers={
                "WWW-Authenticate": www_auth,
                "Content-Type": "application/json",
            },
            content={
                "error": "authentication_required",
                "error_description": "Valid JWT token required. Use resource_metadata to discover authorization server.",
            },
        )

    token = auth_header[7:]  # Remove "Bearer " prefix

    # Validate token
    if _token_verifier:
        claims = await _token_verifier.verify_token(token)
        if not claims:
            # Return 401 with WWW-Authenticate header (RFC 9728 compliant)
            www_auth = (
                f"Bearer "
                f'resource_metadata="{MCP_BASE_URL}/.well-known/oauth-protected-resource"'
            )
            return JSONResponse(
                status_code=401,
                headers={
                    "WWW-Authenticate": www_auth,
                    "Content-Type": "application/json",
                },
                content={
                    "error": "invalid_token",
                    "error_description": "Token validation failed",
                },
            )

        # Store claims in request state for tools to access
        request.state.token_claims = claims

    # For now, return a placeholder response
    # In a full implementation, this would proxy to the MCP server
    return JSONResponse(
        status_code=200,
        content={"status": "authenticated"},
    )


@asynccontextmanager
async def lifespan(app: Starlette) -> AsyncIterator[None]:
    """Lifespan context manager for startup/shutdown."""
    logger.info("Starting Point Topic MCP server with HTTP transport")
    yield
    logger.info("Shutting down Point Topic MCP server")


def create_starlette_app() -> Starlette:
    """Create Starlette app with MCP endpoints."""
    routes = [
        Route("/.well-known/oauth-protected-resource", oauth_metadata_endpoint),
        Route("/health", health_endpoint),
        Route("/mcp", endpoint=mcp_endpoint, methods=["GET", "POST", "DELETE"]),
    ]

    return Starlette(debug=True, routes=routes, lifespan=lifespan)


def create_mcp_server() -> Server:
    """Create and configure MCP server with all tools."""
    server = Server(name="Point Topic MCP", version="0.2.0")

    # Get all tool functions
    tool_functions = get_all_tools()
    tool_lookup = {t.__name__: t for t in tool_functions}

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

        for name, func in tool_lookup.items():
            import inspect

            sig = inspect.signature(func)

            # Build input schema from function signature
            properties: dict[str, dict[str, str]] = {}
            required: list[str] = []

            for param_name, param in sig.parameters.items():
                if param_name.startswith("_"):
                    continue

                # Map Python types to JSON schema
                param_type = param.annotation
                if param_type == str or param_type == inspect.Parameter.empty:
                    json_type = "string"
                elif param_type == int:
                    json_type = "integer"
                elif param_type == float:
                    json_type = "number"
                elif param_type == bool:
                    json_type = "boolean"
                else:
                    json_type = "string"

                properties[param_name] = {"type": json_type}

                if param.default is inspect.Parameter.empty:
                    required.append(param_name)

            tool = Tool(
                name=name,
                description=func.__doc__ or f"Tool: {name}",
                inputSchema={
                    "type": "object",
                    "properties": properties,
                    "required": required,
                },
            )
            tools.append(tool)

        return tools

    @server.call_tool()
    async def call_tool(request: CallToolRequest) -> list[TextContent]:
        """Execute a tool by name."""
        name = request.params.name
        arguments = request.params.arguments or {}

        try:
            tool_func = tool_lookup.get(name)
            if not tool_func:
                return [
                    TextContent(
                        type="text",
                        text=json.dumps({"error": f"Tool not found: {name}"}),
                    )
                ]

            import inspect

            sig = inspect.signature(tool_func)

            # Filter arguments to only include valid parameters
            valid_kwargs = {
                k: v
                for k, v in arguments.items()
                if k in sig.parameters and not k.startswith("_")
            }

            # Execute the tool
            if inspect.iscoroutinefunction(tool_func):
                result = await tool_func(**valid_kwargs)
            else:
                result = tool_func(**valid_kwargs)

            return [TextContent(type="text", text=json.dumps(result, default=str))]

        except Exception as e:
            logger.error(f"Error executing tool {name}: {e}")
            return [TextContent(type="text", text=json.dumps({"error": str(e)}))]

    return server


def main():
    """Main entry point for the HTTP MCP server."""
    import uvicorn

    # Setup logging
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    )

    # Load credentials and initialize token verifier
    creds = load_auth0_credentials()
    global _token_verifier
    _token_verifier = Auth0TokenVerifier(
        auth0_domain=creds["domain"],
        audience=creds["audience"],
        jwks_url=JWKS_URL,
    )

    # Create MCP server (tools will be registered)
    mcp_server = create_mcp_server()
    logger.info(
        f"MCP server created with tools: {list(mcp_server._tool_registry.keys()) if hasattr(mcp_server, '_tool_registry') else 'N/A'}"
    )

    # Create Starlette app
    app = create_starlette_app()

    # Get server configuration
    host = os.getenv("MCP_HTTP_HOST", "0.0.0.0")
    port = int(os.getenv("MCP_HTTP_PORT", "8000"))

    logger.info(f"Starting Point Topic MCP server on {host}:{port}")
    logger.info(f"Auth0 Domain: {AUTH0_DOMAIN}")
    logger.info(f"Audience: {AUTH0_AUDIENCE}")
    logger.info(f"OAuth Metadata: {MCP_BASE_URL}/.well-known/oauth-protected-resource")
    logger.info(f"MCP Endpoint: {MCP_BASE_URL}/mcp")

    # Run the server
    uvicorn.run(app, host=host, port=port)


if __name__ == "__main__":
    main()
