"""
FastAPI dependencies for authentication and authorization.

Provides `get_current_user` and `get_current_active_user` dependency providers
that validate Bearer JWT access tokens and inject the authenticated user model.
"""

from collections.abc import Callable
from uuid import UUID

from fastapi import Depends, Request
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.dependencies import get_db
from app.core.exceptions import ForbiddenException, UnauthorizedException
from app.core.permissions import PermissionCode, resolve_user_permissions
from app.core.security import decode_access_token
from app.core.state_store import is_access_token_blacklisted
from app.models.user import User
from app.repositories.user_repository import UserRepository

http_bearer = HTTPBearer(
    auto_error=False,
    description="Enter your JWT access token directly.",
)

_user_repo = UserRepository()


async def get_current_user(
    request: Request,
    credentials: HTTPAuthorizationCredentials | None = Depends(http_bearer),
    db: AsyncSession = Depends(get_db),
) -> User:
    """
    Validate the incoming Bearer JWT access token (or HttpOnly cookie) and return the User.

    Raises:
        UnauthorizedException: If header/cookie missing, invalid signature, expired, or user not found.
    """
    token: str | None = None
    if credentials and credentials.credentials:
        token = credentials.credentials
    elif "access_token" in request.cookies:
        token = request.cookies.get("access_token")

    if not token:
        raise UnauthorizedException(message="Authentication required. Please provide a Bearer token.")

    if await is_access_token_blacklisted(token):
        raise UnauthorizedException(message="Token has been revoked. Please log in again.")

    payload = decode_access_token(token)
    user_id_str = str(payload.get("sub", ""))
    if not user_id_str:
        raise UnauthorizedException(message="Invalid token claims: missing subject.")

    try:
        user_uuid = UUID(user_id_str)
    except ValueError as exc:
        raise UnauthorizedException(message="Invalid token claims: subject is not a valid UUID.") from exc

    user = await _user_repo.get_by_id(db, user_uuid)
    if not user:
        raise UnauthorizedException(message="User not found.")

    return user


async def get_current_active_user(
    current_user: User = Depends(get_current_user),
) -> User:
    """
    Ensure that the authenticated user is currently active.

    Raises:
        UnauthorizedException: If user is deactivated.
    """
    if not current_user.is_active:
        raise UnauthorizedException(message="User account is deactivated. Please contact your administrator.")
    return current_user


async def effective_permissions(
    current_user: User = Depends(get_current_active_user),
    db: AsyncSession = Depends(get_db),
) -> set[str]:
    """FastAPI caches this resolution within one request, never across users/requests."""
    return await resolve_user_permissions(db, current_user)


def require_permission(
    permission: PermissionCode | str,
) -> Callable[..., object]:
    """
    FastAPI dependency factory enforcing that the authenticated user possesses a specific permission.

    Raises:
        ForbiddenException: If user lacks the specified permission or if the capability is globally disabled.
    """
    perm_code = permission.value if isinstance(permission, PermissionCode) else permission

    async def _dependency(
        current_user: User = Depends(get_current_active_user),
        granted_permissions: set[str] = Depends(effective_permissions),
    ) -> User:
        if perm_code not in granted_permissions:
            raise ForbiddenException(message=f"You do not have the required permission: '{perm_code}'")
        return current_user

    return _dependency


def require_any_permission(
    *permissions: PermissionCode | str,
) -> Callable[..., object]:
    """
    FastAPI dependency factory enforcing that the authenticated user possesses AT LEAST ONE
    of the specified permissions. Superadmins bypass permission checks.
    """
    perm_codes = {p.value if isinstance(p, PermissionCode) else str(p) for p in permissions}

    async def _dependency(
        current_user: User = Depends(get_current_active_user),
        db: AsyncSession = Depends(get_db),
    ) -> User:
        if current_user.is_superadmin:
            return current_user
        granted_permissions = await resolve_user_permissions(db, current_user)
        if not (perm_codes & granted_permissions):
            raise ForbiddenException(
                message=f"You do not have any of the required permissions: {sorted(perm_codes)}"
            )
        return current_user

    return _dependency


def require_superadmin() -> Callable[..., object]:
    """Only platform superadmins may change RBAC configuration."""

    async def _dependency(current_user: User = Depends(get_current_active_user)) -> User:
        if not current_user.is_superadmin:
            raise ForbiddenException(message="This action requires superadmin privileges.")
        return current_user

    return _dependency
