"""Зависимости FastAPI для аутентификации (RBAC): user / guest / admin."""

import uuid
from typing import Annotated

import jwt
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.ext.asyncio import AsyncSession

from core.db import get_session
from core.security import decode_token
from models.user import User
from repositories.users import UserRepository

# `auto_error=False`, чтобы отсутствие заголовка не приводило к автоматической
# ошибке — guest (отсутствие JWT) обрабатывается явно в get_current_user_optional.
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/token", auto_error=False)


async def get_current_user(
    token: Annotated[str | None, Depends(oauth2_scheme)],
    session: Annotated[AsyncSession, Depends(get_session)],
) -> User:
    """Вернуть текущего пользователя по access-токену; 401 если не аутентифицирован."""
    user = await _user_from_token(token, session)
    if user is None:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="not_authenticated")
    return user


async def get_current_user_optional(
    token: Annotated[str | None, Depends(oauth2_scheme)],
    session: Annotated[AsyncSession, Depends(get_session)],
) -> User | None:
    """Вернуть текущего пользователя либо `None` для guest (без ошибки).

    Роль guest в системе — это отсутствие JWT, а не отдельное enum-значение в БД.
    """
    return await _user_from_token(token, session)


async def require_admin(user: Annotated[User, Depends(get_current_user)]) -> User:
    """Требовать роль `admin`; иначе 403."""
    if user.role != "admin":
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="admin_required")
    return user


async def _user_from_token(token: str | None, session: AsyncSession) -> User | None:
    """Общая логика резолва пользователя из access-токена (или None при любой проблеме).

    Заблокированный администратором пользователь (`is_blocked`) трактуется
    так же, как отсутствие пользователя — блокировка действует немедленно,
    не дожидаясь истечения уже выданного access-токена.
    """
    if token is None:
        return None
    try:
        payload = decode_token(token)
    except jwt.PyJWTError:
        return None
    if payload.get("type") != "access":
        return None
    try:
        user_id = uuid.UUID(str(payload.get("sub")))
    except (ValueError, TypeError):
        return None
    user = await UserRepository(session).get_by_id(user_id)
    if user is not None and user.is_blocked:
        return None
    return user
