#!/usr/bin/env python3
"""
Streamable HTTP entrypoint for Apps SDK / ChatGPT connector usage.

Keeps the existing stdio MCP server intact while exposing the same tools over
HTTP for remote integrations.
"""

import os
from contextlib import asynccontextmanager
from typing import Any

import uvicorn
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.routing import Mount, Route
from starlette.types import ASGIApp, Receive, Scope, Send

from server import server


def _as_bool(value: str | None, default: bool = False) -> bool:
    """Parse a truthy environment variable."""
    if value is None:
        return default
    return value.strip().lower() in {"1", "true", "yes", "on"}


def _normalize_path(value: str | None, default: str = "/mcp") -> str:
    """Normalize route path for Starlette mount."""
    if not value:
        return default
    path = value.strip()
    if not path.startswith("/"):
        path = f"/{path}"
    if len(path) > 1 and path.endswith("/"):
        path = path[:-1]
    return path


MCP_HTTP_HOST = os.getenv("MCP_HTTP_HOST", "0.0.0.0")
MCP_HTTP_PORT = int(os.getenv("MCP_HTTP_PORT", "8080"))
MCP_HTTP_PATH = _normalize_path(os.getenv("MCP_HTTP_PATH", "/mcp"))
MCP_HTTP_STATELESS = _as_bool(os.getenv("MCP_HTTP_STATELESS"), default=True)
MCP_HTTP_JSON_RESPONSE = _as_bool(os.getenv("MCP_HTTP_JSON_RESPONSE"), default=False)
MCP_HTTP_LOG_LEVEL = os.getenv("MCP_HTTP_LOG_LEVEL", "info")


class _NormalizeMCPPathMiddleware:
    """Normalize MCP path without redirect to preserve HTTP method/body."""

    def __init__(self, app: ASGIApp, mcp_path: str):
        self.app = app
        self.mcp_path = mcp_path.rstrip("/") or "/mcp"

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        if scope.get("type") == "http" and scope.get("path") == self.mcp_path:
            normalized_path = f"{self.mcp_path}/"
            scope = dict(scope)
            scope["path"] = normalized_path
            if scope.get("raw_path") == self.mcp_path.encode():
                scope["raw_path"] = normalized_path.encode()

        await self.app(scope, receive, send)


def create_app() -> Starlette:
    """Create Starlette app that exposes the MCP server via Streamable HTTP."""
    session_manager = StreamableHTTPSessionManager(
        app=server,
        json_response=MCP_HTTP_JSON_RESPONSE,
        stateless=MCP_HTTP_STATELESS,
    )

    @asynccontextmanager
    async def lifespan(_: Starlette):
        async with session_manager.run():
            yield

    async def health(_: Request) -> JSONResponse:
        return JSONResponse(
            {
                "status": "ok",
                "service": "utforska-data-mcp-http",
                "mcp_path": MCP_HTTP_PATH,
                "stateless": MCP_HTTP_STATELESS,
                "json_response": MCP_HTTP_JSON_RESPONSE,
            }
        )

    async def root(_: Request) -> JSONResponse:
        return JSONResponse(
            {
                "service": "utforska-data-mcp-http",
                "mcp_endpoint": MCP_HTTP_PATH,
                "health_endpoint": "/health",
            }
        )

    class MCPASGIApp:
        async def __call__(self, scope: dict[str, Any], receive: Any, send: Any) -> None:
            await session_manager.handle_request(scope, receive, send)

    app = Starlette(
        routes=[
            Route("/", root),
            Route("/health", health),
            Mount(MCP_HTTP_PATH, app=MCPASGIApp()),
        ],
        lifespan=lifespan,
    )
    app.add_middleware(_NormalizeMCPPathMiddleware, mcp_path=MCP_HTTP_PATH)
    return app


def main() -> None:
    """Run streamable HTTP server."""
    uvicorn.run(
        create_app(),
        host=MCP_HTTP_HOST,
        port=MCP_HTTP_PORT,
        log_level=MCP_HTTP_LOG_LEVEL,
    )


if __name__ == "__main__":
    main()
