"""
Web framework middleware for mongoengine read-write router.

Provides automatic context management for FastAPI, Flask, and Django.
"""

import uuid
from typing import Any, Literal

from .context import RequestContext


def _extract_attr(obj: Any, attr_path: str) -> Any:
    """
    Extract a nested attribute from an object.

    Args:
        obj: The object to extract from
        attr_path: Dot-separated path to the attribute (e.g., "user.id")

    Returns:
        The extracted attribute value, or None if not found
    """
    current = obj
    for part in attr_path.split("."):
        if current is None:
            return None
        if isinstance(current, dict):
            current = current.get(part)
        else:
            current = getattr(current, part, None)
    return current


def _generate_request_id() -> str:
    """Generate a unique request ID."""
    return str(uuid.uuid4())


# ============================================================================
# FastAPI Middleware
# ============================================================================

try:
    from fastapi import Request
    from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
    from starlette.responses import Response

    class MongoRouterMiddleware(BaseHTTPMiddleware):
        """
        FastAPI middleware for automatic request context management.

        This middleware:
        1. Sets the RequestContext at the start of each request
        2. Extracts user_id and request_id from the request
        3. Clears the context at the end of each request (even on exceptions)

        Usage:
            from fastapi import FastAPI
            from mongoengine_rw_router import MongoRouterMiddleware

            app = FastAPI()
            app.add_middleware(MongoRouterMiddleware)
        """

        def __init__(
            self,
            app,
            user_id_attr: str = "user.id",
            request_id_attr: str | None = None,
            generate_request_id: bool = True,
        ):
            """
            Initialize the middleware.

            Args:
                app: The FastAPI application
                user_id_attr: Attribute path to extract user_id (e.g., "user.id")
                request_id_attr: Attribute path to extract request_id (e.g., "state.request_id")
                generate_request_id: Whether to generate a request_id if not found
            """
            super().__init__(app)
            self.user_id_attr = user_id_attr
            self.request_id_attr = request_id_attr
            self.generate_request_id = generate_request_id

        async def dispatch(
            self,
            request: Request,
            call_next: RequestResponseEndpoint,
        ) -> Response:
            """Process the request with context management."""
            # Extract user_id
            user_id = None
            if self.user_id_attr:
                # Try to get from state first (set by auth middleware)
                user_id = _extract_attr(request.state, self.user_id_attr)
                if user_id is None:
                    # Try to get from request
                    user_id = _extract_attr(request, self.user_id_attr)

            # Extract or generate request_id
            request_id = None
            if self.request_id_attr:
                request_id = _extract_attr(request.state, self.request_id_attr)
                if request_id is None:
                    request_id = _extract_attr(request, self.request_id_attr)

            if request_id is None and self.generate_request_id:
                request_id = _generate_request_id()

            # Set context
            RequestContext.set_context(user_id=user_id, request_id=request_id)

            try:
                response = await call_next(request)
                # Add request_id to response headers
                if request_id:
                    response.headers["X-Request-ID"] = request_id
                return response
            finally:
                # Always clear context, even if exception occurred
                RequestContext.reset()

    FASTAPI_AVAILABLE = True

except ImportError:
    FASTAPI_AVAILABLE = False
    MongoRouterMiddleware = None  # type: ignore


# ============================================================================
# Flask Middleware
# ============================================================================

try:
    from flask import Flask, Request, g, request

    class MongoRouterFlaskMiddleware:
        """
        Flask middleware for automatic request context management.

        Usage:
            from flask import Flask
            from mongoengine_rw_router import MongoRouterFlaskMiddleware

            app = Flask(__name__)
            middleware = MongoRouterFlaskMiddleware(app)
        """

        def __init__(
            self,
            app: Flask | None = None,
            user_id_attr: str = "user.id",
            request_id_attr: str | None = None,
            generate_request_id: bool = True,
        ):
            """
            Initialize the middleware.

            Args:
                app: The Flask application
                user_id_attr: Attribute path to extract user_id
                request_id_attr: Attribute path to extract request_id
                generate_request_id: Whether to generate a request_id if not found
            """
            self.user_id_attr = user_id_attr
            self.request_id_attr = request_id_attr
            self.generate_request_id = generate_request_id

            if app is not None:
                self.init_app(app)

        def init_app(self, app: Flask) -> None:
            """Initialize the middleware with a Flask app."""
            app.before_request(self._before_request)
            app.teardown_request(self._teardown_request)
            app.after_request(self._after_request)

        def _before_request(self) -> None:
            """Set context before request processing."""
            # Extract user_id from g or request
            user_id = None
            if self.user_id_attr:
                user_id = _extract_attr(g, self.user_id_attr)
                if user_id is None:
                    user_id = _extract_attr(request, self.user_id_attr)

            # Extract or generate request_id
            request_id = None
            if self.request_id_attr:
                request_id = _extract_attr(g, self.request_id_attr)
                if request_id is None:
                    request_id = _extract_attr(request, self.request_id_attr)

            if request_id is None and self.generate_request_id:
                request_id = _generate_request_id()
                g.request_id = request_id

            # Set context
            RequestContext.set_context(user_id=user_id, request_id=request_id)

        def _after_request(self, response):
            """Add request_id to response headers."""
            request_id = getattr(g, "request_id", None)
            if request_id:
                response.headers["X-Request-ID"] = request_id
            return response

        def _teardown_request(self, exception):
            """Clear context after request processing."""
            RequestContext.reset()

    FLASK_AVAILABLE = True

except ImportError:
    FLASK_AVAILABLE = False
    MongoRouterFlaskMiddleware = None  # type: ignore


# ============================================================================
# Django Middleware
# ============================================================================

try:
    from django.http import HttpRequest, HttpResponse

    class MongoRouterDjangoMiddleware:
        """
        Django middleware for automatic request context management.

        Usage:
            # In settings.py
            MIDDLEWARE = [
                ...
                'mongoengine_rw_router.MongoRouterDjangoMiddleware',
                ...
            ]

            # Or with custom configuration
            MIDDLEWARE = [
                ...
                'myapp.middleware.CustomMongoRouterMiddleware',
                ...
            ]
        """

        def __init__(self, get_response):
            """
            Initialize the middleware.

            Args:
                get_response: The next middleware or view in the chain
            """
            self.get_response = get_response
            self.user_id_attr = "user.id"
            self.request_id_attr = None
            self.generate_request_id = True

        def __call__(self, request: HttpRequest) -> HttpResponse:
            """Process the request with context management."""
            # Extract user_id
            user_id = None
            if self.user_id_attr:
                user_id = _extract_attr(request, self.user_id_attr)

            # Extract or generate request_id
            request_id = None
            if self.request_id_attr:
                request_id = _extract_attr(request, self.request_id_attr)

            if request_id is None and self.generate_request_id:
                request_id = _generate_request_id()

            # Set context
            RequestContext.set_context(user_id=user_id, request_id=request_id)

            try:
                response = self.get_response(request)
                # Add request_id to response headers
                if request_id:
                    response["X-Request-ID"] = request_id
                return response
            finally:
                # Always clear context
                RequestContext.reset()

        @classmethod
        def create(cls, **kwargs) -> type:
            """
            Create a custom middleware class with specific configuration.

            Args:
                **kwargs: Configuration options (user_id_attr, request_id_attr, etc.)

            Returns:
                A custom middleware class

            Example:
                CustomMiddleware = MongoRouterDjangoMiddleware.create(
                    user_id_attr='auth.user.id',
                    request_id_attr='id',
                )
            """

            class CustomMiddleware(cls):
                def __init__(self, get_response):
                    super().__init__(get_response)
                    for key, value in kwargs.items():
                        if hasattr(self, key):
                            setattr(self, key, value)

            return CustomMiddleware

    DJANGO_AVAILABLE = True

except ImportError:
    DJANGO_AVAILABLE = False
    MongoRouterDjangoMiddleware = None  # type: ignore


def get_middleware(
    framework: Literal["fastapi", "flask", "django"],
    **kwargs,
):
    """
    Get the appropriate middleware class for the specified framework.

    Args:
        framework: The framework name ("fastapi", "flask", or "django")
        **kwargs: Additional configuration for the middleware

    Returns:
        The middleware class for the specified framework

    Raises:
        ImportError: If the framework is not available
        ValueError: If the framework is not supported
    """
    if framework == "fastapi":
        if not FASTAPI_AVAILABLE:
            raise ImportError("FastAPI is not installed")
        return MongoRouterMiddleware
    elif framework == "flask":
        if not FLASK_AVAILABLE:
            raise ImportError("Flask is not installed")
        return MongoRouterFlaskMiddleware
    elif framework == "django":
        if not DJANGO_AVAILABLE:
            raise ImportError("Django is not installed")
        if kwargs:
            return MongoRouterDjangoMiddleware.create(**kwargs)
        return MongoRouterDjangoMiddleware
    else:
        raise ValueError(f"Unsupported framework: {framework}")


__all__ = [
    "MongoRouterMiddleware",
    "MongoRouterFlaskMiddleware",
    "MongoRouterDjangoMiddleware",
    "get_middleware",
]
