"""FastAPI dependencies: DB session, current user, RBAC."""
from uuid import UUID

from fastapi import Depends, Request
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from app.core.config import settings
from app.core.exceptions import AuthError, ForbiddenError
from app.core.rbac import Role, has_any_role
from app.core.security import decode_access_token
from app.db.base import get_db
from app.models import UserModel


async def get_current_user(
    request: Request,
    db: AsyncSession = Depends(get_db),
) -> UserModel:
    """Extract user from JWT in Authorization header or session cookie."""
    token: str | None = None

    # Try Authorization header first
    auth_header = request.headers.get("Authorization")
    if auth_header and auth_header.startswith("Bearer "):
        token = auth_header[7:]

    # Fall back to session cookie
    if not token:
        cookie_name = settings.SESSION_COOKIE_NAME
        token = request.cookies.get(cookie_name)

    if not token:
        raise AuthError("No authentication token provided")

    payload = decode_access_token(token)
    if not payload:
        raise AuthError("Invalid or expired token")

    user_id = payload.get("sub")
    if not user_id:
        raise AuthError("Token missing user ID")

    result = await db.execute(
        select(UserModel)
        .where(UserModel.id == UUID(user_id))
        .options(selectinload(UserModel.roles))
    )
    user = result.scalar_one_or_none()
    if not user or not user.is_active:
        raise AuthError("User not found or inactive")

    # Attach roles as string list for easy checking
    user._role_names = [r.name for r in user.roles]  # type: ignore
    return user


def require_roles(*roles: Role):
    """Dependency factory: require the user to have at least one of the given roles."""
    async def checker(
        user: UserModel = Depends(get_current_user),
    ) -> UserModel:
        user_roles: list[str] = getattr(user, "_role_names", [])
        if not has_any_role(user_roles, list(roles)):
            raise ForbiddenError(f"Requires one of: {[r.value for r in roles]}")
        return user
    return checker


async def get_current_user_optional(
    request: Request,
    db: AsyncSession = Depends(get_db),
) -> UserModel | None:
    """Like get_current_user but returns None instead of raising (for health checks)."""
    try:
        return await get_current_user(request, db)
    except AuthError:
        return None
