"""FastAPI middleware and dependencies for JWT validation."""

from fastapi import Depends, HTTPException, Request
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import JSONResponse
from starlette.types import ASGIApp

from ..exceptions import AuthenticationError
from ..jwt import JWTValidator, TokenClaims


class JWTValidationMiddleware(BaseHTTPMiddleware):
    """Middleware that validates JWTs on every request.

    This middleware:
    1. Extracts JWT from Authorization header
    2. Validates the token using JWKS
    3. Populates request.state with claims

    Sets on request.state:
    - user: TokenClaims object
    - tenant_id: str
    - user_id: str
    - permissions: list[str]
    - roles: list[str]

    Example:
        from fastapi import FastAPI
        from vikdata_auth.middleware import JWTValidationMiddleware

        app = FastAPI()
        app.add_middleware(
            JWTValidationMiddleware,
            jwks_url="https://api.vikdata.com/.well-known/jwks.json",
        )
    """

    def __init__(
        self,
        app: ASGIApp,
        jwks_url: str,
        audience: str | list[str] = "vikdata",
        issuer: str = "https://api.vikdata.com",
        exclude_paths: list[str] | None = None,
        exclude_prefixes: list[str] | None = None,
    ):
        """Initialize JWT validation middleware.

        Args:
            app: ASGI application
            jwks_url: URL to fetch JWKS from
            audience: Expected JWT audience
            issuer: Expected JWT issuer
            exclude_paths: Exact paths to skip authentication (default: ["/health", "/metrics"])
            exclude_prefixes: Path prefixes to skip authentication (default: ["/docs", "/openapi.json", "/redoc"])
        """
        super().__init__(app)
        self.validator = JWTValidator(
            jwks_url=jwks_url,
            audience=audience,
            issuer=issuer,
        )
        self.exclude_paths = set(exclude_paths or ["/health", "/metrics"])
        self.exclude_prefixes = exclude_prefixes or ["/docs", "/openapi.json", "/redoc"]

    def _should_skip(self, path: str) -> bool:
        """Check if path should skip authentication."""
        if path in self.exclude_paths:
            return True
        return any(path.startswith(prefix) for prefix in self.exclude_prefixes)

    async def dispatch(self, request: Request, call_next):
        """Process request through middleware."""
        # Skip excluded paths
        if self._should_skip(request.url.path):
            return await call_next(request)

        # Extract token from Authorization header
        auth_header = request.headers.get("Authorization")
        if not auth_header:
            return JSONResponse(
                {"error": "unauthorized", "message": "Missing Authorization header"},
                status_code=401,
                headers={"WWW-Authenticate": "Bearer"},
            )

        if not auth_header.startswith("Bearer "):
            return JSONResponse(
                {"error": "unauthorized", "message": "Invalid Authorization header format"},
                status_code=401,
                headers={"WWW-Authenticate": "Bearer"},
            )

        token = auth_header[7:]  # Remove "Bearer "

        try:
            claims = await self.validator.validate(token)

            # Populate request.state
            request.state.user = claims
            request.state.tenant_id = claims.tenant_id
            request.state.user_id = claims.user_id
            request.state.permissions = claims.permissions
            request.state.roles = claims.roles

        except AuthenticationError as e:
            return JSONResponse(
                {"error": "unauthorized", "message": str(e)},
                status_code=401,
                headers={"WWW-Authenticate": "Bearer"},
            )

        return await call_next(request)


# Security scheme for OpenAPI docs
bearer_scheme = HTTPBearer(auto_error=False)


class AuthDependency:
    """FastAPI dependency for JWT validation.

    Use this instead of middleware when you need more control over which
    endpoints require authentication.

    Example:
        from vikdata_auth.middleware import AuthDependency
        from vikdata_auth.jwt import TokenClaims

        auth = AuthDependency(jwks_url="https://api.vikdata.com/.well-known/jwks.json")

        @app.get("/protected")
        async def protected(claims: TokenClaims = Depends(auth)):
            return {"user_id": claims.user_id}
    """

    def __init__(
        self,
        jwks_url: str,
        audience: str | list[str] = "vikdata",
        issuer: str = "https://api.vikdata.com",
    ):
        """Initialize auth dependency.

        Args:
            jwks_url: URL to fetch JWKS from
            audience: Expected JWT audience
            issuer: Expected JWT issuer
        """
        self.validator = JWTValidator(
            jwks_url=jwks_url,
            audience=audience,
            issuer=issuer,
        )

    async def __call__(
        self,
        credentials: HTTPAuthorizationCredentials | None = Depends(bearer_scheme),
    ) -> TokenClaims:
        """Validate JWT and return claims."""
        if not credentials:
            raise HTTPException(
                status_code=401,
                detail="Missing Authorization header",
                headers={"WWW-Authenticate": "Bearer"},
            )

        try:
            return await self.validator.validate(credentials.credentials)
        except AuthenticationError as e:
            raise HTTPException(
                status_code=401,
                detail=str(e),
                headers={"WWW-Authenticate": "Bearer"},
            )


def get_current_user(request: Request) -> TokenClaims:
    """Get current user from request.state (set by middleware).

    Example:
        @app.get("/protected")
        async def protected(user: TokenClaims = Depends(get_current_user)):
            return {"user_id": user.user_id}

    Args:
        request: FastAPI request object

    Returns:
        TokenClaims object with validated claims

    Raises:
        HTTPException: 401 if not authenticated
    """
    user = getattr(request.state, "user", None)
    if not user:
        raise HTTPException(status_code=401, detail="Not authenticated")
    return user


def get_tenant_id(request: Request) -> str:
    """Get tenant ID from request.state (set by middleware).

    Args:
        request: FastAPI request object

    Returns:
        Tenant ID string

    Raises:
        HTTPException: 401 if not authenticated
    """
    tenant_id = getattr(request.state, "tenant_id", None)
    if not tenant_id:
        raise HTTPException(status_code=401, detail="Not authenticated")
    return tenant_id


def get_user_id(request: Request) -> str:
    """Get user ID from request.state (set by middleware).

    Args:
        request: FastAPI request object

    Returns:
        User ID string

    Raises:
        HTTPException: 401 if not authenticated
    """
    user_id = getattr(request.state, "user_id", None)
    if not user_id:
        raise HTTPException(status_code=401, detail="Not authenticated")
    return user_id
