"""ASGI middleware for request-level PyAuthz context.

Attaches the PyAuthz engine and resolved Subject to the request state,
making them available to route handlers and dependencies. Also registers
a Denied exception handler to produce 403 JSON responses.
"""

from __future__ import annotations

import inspect
from collections.abc import Awaitable, Callable
from typing import Any

from fastapi import FastAPI, Request, Response
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.types import ASGIApp

from pyauthz import PyAuthz, Subject
from pyauthz.core.errors import Denied

# Type for the user-provided subject resolver function.
# It receives a Request and returns a Subject (sync or async).
SubjectResolver = Callable[[Request], Subject | Awaitable[Subject]]


class PyAuthzMiddleware(BaseHTTPMiddleware):
    """ASGI middleware that attaches PyAuthz context to every request.

    Resolves the Subject from the request using the provided resolver function,
    then stores both the engine and subject on `request.state` for downstream
    access via FastAPI dependencies.

    Also registers a Denied exception handler on the FastAPI app to convert
    authorization failures into structured 403 JSON responses.

    Args:
        app: The ASGI application.
        pyauthz: The PyAuthz engine instance.
        subject_resolver: A callable that takes a Request and returns a Subject.
            Can be sync or async.

    Example:
        app.add_middleware(
            PyAuthzMiddleware,
            pyauthz=rp,
            subject_resolver=resolve_user,
        )
    """

    def __init__(
        self,
        app: ASGIApp,
        pyauthz: PyAuthz,
        subject_resolver: SubjectResolver,
    ) -> None:
        super().__init__(app)
        self.pyauthz = pyauthz
        self.subject_resolver = subject_resolver

    async def dispatch(self, request: Request, call_next: Any) -> Response:
        """Process the request: resolve subject, attach context."""
        # Resolve the subject from the request
        result = self.subject_resolver(request)
        if inspect.isawaitable(result):
            subject = await result
        else:
            subject = result

        # Attach to request state for dependency injection
        request.state.pyauthz = self.pyauthz
        request.state.subject = subject

        return await call_next(request)


def setup_pyauthz(
    app: FastAPI,
    pyauthz: PyAuthz,
    subject_resolver: SubjectResolver,
) -> None:
    """Convenience function to set up PyAuthz on a FastAPI app.

    Adds the middleware and registers the Denied exception handler in one call.

    Example:
        from pyauthz_fastapi import setup_pyauthz
        setup_pyauthz(app, rp, resolve_user)
    """
    app.add_middleware(PyAuthzMiddleware, pyauthz=pyauthz, subject_resolver=subject_resolver)
    app.add_exception_handler(Denied, denied_exception_handler)


async def denied_exception_handler(request: Request, exc: Denied) -> JSONResponse:
    """Convert a Denied exception to a structured 403 JSON response."""
    decision = exc.decision
    return JSONResponse(
        status_code=403,
        content={
            "error": "forbidden",
            "detail": decision.explain(),
            "matched_policies": decision.matched_policies,
            "reasoning": decision.reasoning,
        },
    )
