"""
Glyphh Runtime Server — single FastAPI application definition.

Importable as ``glyphh.server:app`` for both pip-installed CLI usage and
Docker/production deployments.  The repo-root ``main.py`` is a thin shim
that re-exports this app.
"""

import logging
from contextlib import asynccontextmanager
from datetime import datetime
from typing import AsyncGenerator, Optional

from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse

from infrastructure.config import get_settings, validate_settings
from infrastructure.database import init_db, close_db, async_session_maker
from shared.exceptions import GlyphhRuntimeException
from glyphh.licensing import load_license, set_current_license
from shared.middleware import (
    CorrelationIDMiddleware,
    LoggingMiddleware,
)
from domains.models.manager import ModelManager
from domains.resources.manager import ResourceManager

# Configure structured logging
logging.basicConfig(
    level=logging.INFO,
    format='{"timestamp": "%(asctime)s", "level": "%(levelname)s", "logger": "%(name)s", "message": "%(message)s"}'
)
logger = logging.getLogger(__name__)

settings = get_settings()
_start_time = datetime.utcnow()

# Global model manager instance
model_manager: Optional[ModelManager] = None
resource_manager: Optional[ResourceManager] = None


def get_model_manager() -> ModelManager:
    """Dependency for getting model manager"""
    return model_manager


def get_resource_manager() -> ResourceManager:
    """Dependency for getting resource manager"""
    return resource_manager


@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
    """Application lifespan management"""
    global model_manager, resource_manager

    # Startup
    logger.info("Starting Glyphh Runtime...")

    # Validate configuration
    try:
        validate_settings()
        logger.info("Configuration validated")
    except ValueError as e:
        logger.error(f"Configuration error: {e}")
        raise

    # Load license (determines tier and limits)
    license_info = load_license()
    app.state.license = license_info
    set_current_license(license_info)
    logger.info(f"License: tier={license_info.tier}, org={license_info.org_id}")

    await init_db()
    logger.info("Database initialized")

    # Initialize model manager
    model_manager = ModelManager(async_session_maker)
    logger.info("Model manager initialized")

    # Initialize resource manager
    resource_manager = ResourceManager(async_session_maker)
    logger.info("Resource manager initialized")

    # Initialize MCP session managers
    from domains.auth.service import AuthService
    from domains.query.service import QueryService
    from domains.mcp.app import create_mcp_session_managers

    query_service = QueryService(model_manager, async_session_maker)
    auth_service = AuthService()
    json_manager, sse_manager = create_mcp_session_managers(query_service, auth_service)
    app.state.mcp_session_managers = (json_manager, sse_manager)
    logger.info("MCP Streamable HTTP server initialized (JSON + SSE)")

    # Resume any incomplete staged exemplar encoding from a previous run
    try:
        await model_manager.resume_staged_encoding()
    except Exception as e:
        logger.warning(f"Staged encoding resume failed: {e}")

    # Start both MCP session manager lifecycles
    async with json_manager.run():
        async with sse_manager.run():
            yield

    # Graceful shutdown
    logger.info("Shutting down Glyphh Runtime...")
    logger.info("Draining connections...")
    await close_db()
    logger.info("Database connections closed")
    logging.shutdown()
    logger.info("Shutdown complete")


# Create FastAPI application
app = FastAPI(
    title="Glyphh Runtime",
    description="Execution environment for directory-based models",
    version="1.3.8",
    docs_url="/docs" if settings.enable_docs else None,
    redoc_url="/redoc" if settings.enable_docs else None,
    lifespan=lifespan,
)

# CORS middleware
if settings.cors_allow_all:
    app.add_middleware(
        CORSMiddleware,
        allow_origins=["*"],
        allow_credentials=False,
        allow_methods=["*"],
        allow_headers=["*"],
    )
else:
    origins = settings.cors_origins_production or settings.cors_origins
    app.add_middleware(
        CORSMiddleware,
        allow_origins=origins,
        allow_credentials=True,
        allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
        allow_headers=["*"],
    )

# Custom middleware
app.add_middleware(CorrelationIDMiddleware)
app.add_middleware(LoggingMiddleware)


# Global exception handlers
@app.exception_handler(GlyphhRuntimeException)
async def runtime_exception_handler(request: Request, exc: GlyphhRuntimeException) -> JSONResponse:
    """Handle custom runtime exceptions with CORS headers"""
    response = JSONResponse(
        status_code=exc.status_code,
        content={
            "error": {
                "code": exc.error_code,
                "message": exc.message,
                "details": exc.details,
                "correlation_id": getattr(request.state, "correlation_id", "unknown"),
                "timestamp": datetime.utcnow().isoformat() + "Z"
            }
        }
    )
    origin = request.headers.get("origin")
    if origin and _is_allowed_origin(origin):
        response.headers["Access-Control-Allow-Origin"] = origin
        response.headers["Access-Control-Allow-Credentials"] = "true"
    return response


@app.exception_handler(Exception)
async def general_exception_handler(request: Request, exc: Exception) -> JSONResponse:
    """Handle unexpected exceptions with CORS headers"""
    logger.error(f"Unexpected error: {exc}", exc_info=True)
    response = JSONResponse(
        status_code=500,
        content={
            "error": {
                "code": "INTERNAL_SERVER_ERROR",
                "message": "An unexpected error occurred",
                "correlation_id": getattr(request.state, "correlation_id", "unknown"),
                "timestamp": datetime.utcnow().isoformat() + "Z"
            }
        }
    )
    origin = request.headers.get("origin")
    if origin and _is_allowed_origin(origin):
        response.headers["Access-Control-Allow-Origin"] = origin
        response.headers["Access-Control-Allow-Credentials"] = "true"
    return response


def _is_allowed_origin(origin: str) -> bool:
    """Check if origin is in allowed CORS origins list."""
    if settings.cors_allow_all:
        return True
    import fnmatch
    origins = settings.cors_origins_production or settings.cors_origins
    for allowed in origins:
        if allowed == "*" or allowed == origin:
            return True
        if fnmatch.fnmatch(origin, allowed):
            return True
    return False


# Import and include routers
from api.routes import (
    health_router,
    org_scoped_router,
    org_level_router,
    listeners_router,
    tokens_router,
    ui_router,
)

app.include_router(health_router)
app.include_router(tokens_router)
app.include_router(ui_router)
# listeners_router must come before org_scoped_router (more specific prefix)
app.include_router(listeners_router)
# org_level_router (/{org_id}/models) before org_scoped_router (/{org_id}/{model_id}/...)
app.include_router(org_level_router)
app.include_router(org_scoped_router)


# MCP routing middleware — wraps the ASGI app to intercept /{org_id}/{model_id}/mcp
# requests and forward them to the MCP SDK's Streamable HTTP handler.
from domains.mcp.app import MCPRoutingMiddleware

app.add_middleware(MCPRoutingMiddleware, mcp_app_getter=lambda: getattr(app.state, "mcp_session_managers", None))


# ── Web UI ──────────────────────────────────────────────────────────────────
# Serve the Vite-built React dashboard from public/dist/.
# Must come after all API routers so /{org_id}/... paths aren't shadowed.

from pathlib import Path
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse

_DIST_DIR = Path(__file__).resolve().parent / "public" / "dist"
_PUBLIC_DIR = Path(__file__).resolve().parent / "public"

if _DIST_DIR.is_dir():
    # SPA: serve index.html for all non-API, non-asset paths
    @app.get("/", include_in_schema=False)
    async def serve_ui():
        return FileResponse(str(_DIST_DIR / "index.html"))

    @app.get("/{path:path}", include_in_schema=False)
    async def serve_spa(path: str):
        # Never SPA-fallback API paths — let them 404 naturally
        if path.startswith("api/"):
            from fastapi.responses import JSONResponse
            return JSONResponse({"detail": "Not Found"}, status_code=404)
        # Serve static asset if it exists, otherwise SPA fallback
        file = _DIST_DIR / path
        if file.is_file():
            return FileResponse(str(file))
        return FileResponse(str(_DIST_DIR / "index.html"))

    app.mount("/assets", StaticFiles(directory=str(_DIST_DIR / "assets")), name="assets")
elif _PUBLIC_DIR.is_dir():
    # Fallback: legacy vanilla UI (dev only)
    @app.get("/", include_in_schema=False)
    async def serve_ui_legacy():
        return FileResponse(str(_PUBLIC_DIR / "index.html"))

    app.mount("/public", StaticFiles(directory=str(_PUBLIC_DIR)), name="public")
