"""FastAPI application entrypoint for the HIEROMEM service."""
# flake8: noqa

from __future__ import annotations

import logging
from datetime import datetime
from typing import Any, Awaitable, Callable

from fastapi import Depends, FastAPI, Request, Response
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware

from hieromem_api.auth import require_api_key
from hieromem_api.deps import get_memory_engine
from hieromem_api.instrumentation.logging import get_logger, setup_logging
from hieromem_api.instrumentation.prometheus import (
    metrics_endpoint,
    metrics_middleware,
)
from hieromem_api.instrumentation.tracing import (
    setup_tracing,
    shutdown_tracing,
    trace_request,
)
from hieromem_api.models.requests import (
    DualRecallRequest,
    ForgetRequest,
    RecallRequest,
    RememberRequest,
    SymbolicSearchRequest,
)
from hieromem_api.models.responses import (
    DualRecallResponse,
    ForgetResponse,
    HealthResponse,
    InfoResponse,
    MetricResponse,
    RecallResponse,
    RememberResponse,
    SymbolicSearchResponse,
)
from hieromem_api.routes import init_routes
from hieromem_api.routes.health import health as health_route, system_info as info_route
from hieromem_api.routes.memory import (
    dual_recall as memory_dual_recall,
    forget as memory_forget,
    metrics as memory_metrics,
    recall as memory_recall,
    remember as memory_remember,
    symbolic_search as memory_symbolic_search,
)
from hieromem_api.middleware.idempotency import IdempotencyMiddleware
from hieromem_api.middleware.input_validation import InputValidationMiddleware
from hieromem_api.middleware.quota import QuotaMiddleware
from hieromem_api.middleware.security_headers import SecurityHeadersMiddleware
from hieromem_api.middleware.size_limit import RequestSizeLimitMiddleware
from hieromem_api.settings import get_settings
from hieromem_api.tenants import Tenant, get_tenant_registry

# --------------------------------------------------------------------------- #
# Application factory
# --------------------------------------------------------------------------- #


def _configure_instrumentation() -> Any:
    """Initialise logging and tracing for the service."""

    setup_logging()
    return setup_tracing(service_name="hieromem_api")


def create_app() -> FastAPI:
    """Create and configure the FastAPI application instance."""

    tracer = _configure_instrumentation()
    settings = get_settings()

    app = FastAPI(
        title="HIEROMEM API", version="0.2.0", description=(
            "Cognitive memory microservice implementing the HIEROMEM theoretical "
            "framework with multi-tenant, multi-modal retrieval capabilities."), )

    # Store shared handles for downstream routers and startup hooks.
    app.state.settings = settings
    app.state.memory = get_memory_engine()
    app.state.logger = get_logger("hieromem_api.main")
    app.state.tracer = tracer

    # Basic CORS policy to simplify integration with demos and dashboards.
    raw_origins = [origin.strip()
                   for origin in settings.cors_allow_origins.split(",")]
    allow_origins = [origin for origin in raw_origins if origin]
    
    # Add default development and Lovable origins
    default_origins = [
        "http://localhost:5173",
        "https://*.lovableproject.com",
    ]
    allow_origins.extend(default_origins)
    
    if not allow_origins:
        allow_origins = ["*"]
    app.add_middleware(
        CORSMiddleware,
        allow_origins=allow_origins,
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )

    app.add_middleware(GZipMiddleware, minimum_size=settings.gzip_minimum_size)



    # Add request size limit middleware (10MB)
    app.add_middleware(
        RequestSizeLimitMiddleware,
        max_size_bytes=10 * 1024 * 1024
    )

    allowed_content_types = {
        ct.strip().lower()
        for ct in settings.allowed_content_types.split(",")
        if ct.strip()
    }

    app.add_middleware(
        InputValidationMiddleware,
        allowed_content_types=allowed_content_types,
        max_query_params=settings.max_query_params,
        max_query_length=settings.max_query_length,
    )

    app.add_middleware(SecurityHeadersMiddleware)

    # Add idempotency middleware
    app.add_middleware(IdempotencyMiddleware)
    
    # Add quota enforcement middleware
    app.add_middleware(QuotaMiddleware)


    # Prometheus-friendly metrics middleware.
    CallNext = Callable[[Request], Awaitable[Response]]

    @app.middleware("http")
    async def _metrics_middleware(
        request: Request,
        call_next: CallNext
    ) -> Response:
        return await metrics_middleware(request, call_next)

    # Request ID tracking middleware for correlation
    @app.middleware("http")
    async def _request_id_middleware(
        request: Request,
        call_next: CallNext
    ) -> Response:
        import uuid
        # Check if request already has an ID (from upstream proxy)
        request_id = request.headers.get("X-Request-ID")
        if not request_id:
            request_id = str(uuid.uuid4())

        # Store in request state for access in endpoints
        request.state.request_id = request_id

        # Process request
        response = await call_next(request)

        # Add request ID to response headers
        response.headers["X-Request-ID"] = request_id

        return response

    # Distributed tracing middleware to emit per-request spans
    @app.middleware("http")
    async def _tracing_middleware(
        request: Request,
        call_next: CallNext,
    ) -> Response:
        return await trace_request(tracer, request, call_next)

    # Register the modular routers (memory, events, admin, system).
    init_routes(app)

    _register_compatibility_routes(app)
    _register_startup_hook(app)

    return app


def _register_startup_hook(app: FastAPI) -> None:
    """Emit a concise startup log once the application is ready."""

    logger: logging.Logger = app.state.logger

    @app.on_event("startup")
    def _startup() -> None:
        memory = get_memory_engine()
        logger.info(
            "[startup] HIEROMEM engine ready | episodes=%s | namespaces=%s",
            len(memory.episodes),
            getattr(memory, "_episodes_by_namespace", {}).keys()
            if getattr(memory, "_multi_tenant", False)
            else "single",
        )

    @app.on_event("shutdown")
    def _shutdown() -> None:
        logger.info("[shutdown] HIEROMEM engine shutting down gracefully")
        # Close any open connections
        try:
            memory = get_memory_engine()
            store = memory.store
            graph_store = getattr(store, "graph_store", None) if store else None
            if graph_store is not None:
                driver = getattr(graph_store, "_driver", None)
                if driver:
                    driver.close()
                    logger.info("[shutdown] Closed Neo4j driver")
        except Exception as e:
            logger.warning(f"[shutdown] Error closing connections: {e}")
        finally:
            shutdown_tracing()



def _register_compatibility_routes(app: FastAPI) -> None:
    """Expose top-level routes for backwards compatibility."""

    @app.post(
        "/remember",
        response_model=RememberResponse,
        tags=["Memory"],
    )
    def remember_endpoint(
        req: RememberRequest, tenant: Tenant = Depends(require_api_key)
    ) -> RememberResponse:
        return memory_remember(req, tenant)

    @app.post(
        "/recall",
        response_model=RecallResponse,
        tags=["Memory"],
    )
    def recall_endpoint(
        req: RecallRequest, tenant: Tenant = Depends(require_api_key)
    ) -> RecallResponse:
        return memory_recall(req, tenant)

    @app.post(
        "/dual-recall",
        response_model=DualRecallResponse,
        tags=["Memory"],
    )
    def dual_recall_endpoint(
        req: DualRecallRequest, tenant: Tenant = Depends(require_api_key)
    ) -> DualRecallResponse:
        return memory_dual_recall(req, tenant)

    @app.post(
        "/symbolic-search",
        response_model=SymbolicSearchResponse,
        tags=["Memory"],
    )
    def symbolic_search_endpoint(
        req: SymbolicSearchRequest, tenant: Tenant = Depends(require_api_key)
    ) -> SymbolicSearchResponse:
        return memory_symbolic_search(req, tenant)

    @app.post(
        "/forget",
        response_model=ForgetResponse,
        tags=["Memory"],
    )
    def forget_endpoint(
        req: ForgetRequest, tenant: Tenant = Depends(require_api_key)
    ) -> ForgetResponse:
        return memory_forget(req, tenant)

    @app.get(
        "/metrics",
        response_model=MetricResponse,
        tags=["Metrics"],
    )
    def metrics_endpoint_json(
        namespace: str | None = None, tenant: Tenant = Depends(require_api_key)
    ) -> MetricResponse:
        return memory_metrics(namespace, tenant)

    @app.get(
        "/metrics.prom",
        include_in_schema=False,
    )
    def metrics_endpoint_prom(
            tenant: Tenant = Depends(require_api_key)) -> Response:
        content, media_type = metrics_endpoint()
        return Response(content=content, media_type=media_type)

    @app.get("/health", response_model=HealthResponse, tags=["System"])
    def health_endpoint() -> HealthResponse:
        return health_route()
    
    @app.get("/health/detailed", tags=["System"])
    def detailed_health_endpoint() -> dict[str, Any]:
        """Detailed health check including database, memory, tenant registry, and auth."""
        from hieromem_api.tenants import get_tenant_registry
        import time
        
        start_time = time.time()
        services = {}
        
        try:
            # Check memory engine
            memory = get_memory_engine()
            services["memory_engine"] = {
                "status": "healthy",
                "episodes": len(getattr(memory, "episodes", [])),
                "backend": memory.store.__class__.__name__ if hasattr(memory, "store") else "unknown"
            }
        except Exception as e:
            services["memory_engine"] = {"status": "unhealthy", "error": str(e)}
        
        try:
            # Check tenant registry
            registry = get_tenant_registry()
            tenant_count = len(registry.list_all()) if hasattr(registry, "list_all") else 0
            services["tenant_registry"] = {
                "status": "healthy",
                "tenant_count": tenant_count
            }
        except Exception as e:
            services["tenant_registry"] = {"status": "unhealthy", "error": str(e)}
        
        try:
            # Check database connectivity
            from hieromem_api.settings import get_settings
            settings = get_settings()
            services["database"] = {
                "status": "healthy",
                "type": "sqlite"
            }
        except Exception as e:
            services["database"] = {"status": "unhealthy", "error": str(e)}
        
        # Calculate response time
        response_time_ms = (time.time() - start_time) * 1000
        
        # Determine overall status
        all_healthy = all(
            svc.get("status") == "healthy" 
            for svc in services.values()
        )
        
        return {
            "status": "healthy" if all_healthy else "degraded",
            "timestamp": datetime.utcnow().isoformat(),
            "response_time_ms": round(response_time_ms, 2),
            "services": services,
            "version": "0.2.0"
        }

    @app.get("/info", response_model=InfoResponse, tags=["System"])
    def info_endpoint() -> InfoResponse:
        return info_route()



# --------------------------------------------------------------------------- #
# Module level ASGI application
# --------------------------------------------------------------------------- #

app = create_app()

memory = get_memory_engine()


class AttrDict(dict[str, Any]):
    """Dict subclass offering attribute access for compatibility shims."""

    def __getattr__(self, item: str) -> Any:  # pragma: no cover - thin convenience wrapper
        try:
            return self[item]
        except KeyError as exc:
            raise AttributeError(item) from exc


def _default_tenant() -> Tenant:
    """Return the registry's default tenant for internal calls."""

    return get_tenant_registry().default_tenant()


def remember(req: RememberRequest) -> RememberResponse:
    """Compatibility shim returning fully-typed responses for scripts/tests."""

    return memory_remember(req, _default_tenant())


def recall(req: RecallRequest) -> RecallResponse:
    """Compatibility shim returning fully-typed responses for scripts/tests."""

    return memory_recall(req, _default_tenant())


def dual_recall(req: DualRecallRequest) -> DualRecallResponse:
    """Compatibility shim returning fully-typed responses for scripts/tests."""

    return memory_dual_recall(req, _default_tenant())


def forget(req: ForgetRequest) -> ForgetResponse:
    """Compatibility shim returning fully-typed responses for scripts/tests."""

    return memory_forget(req, _default_tenant())


def metrics(namespace: str | None = None) -> MetricResponse:
    """Compatibility shim returning fully-typed responses for scripts/tests."""

    return memory_metrics(namespace, _default_tenant())


def health() -> HealthResponse:
    """Compatibility shim returning fully-typed responses for scripts/tests."""

    return health_route()


__all__ = [
    "app",
    "create_app",
    "memory",
    "remember",
    "recall",
    "dual_recall",
    "forget",
    "metrics",
    "health",
]
