"""FastAPI dependencies: DB session, current user, role guards."""
from collections.abc import Callable, Iterable

import jwt
from fastapi import Depends, HTTPException, Request, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy import select
from sqlalchemy.orm import Session

from app.core.security import decode_token
from app.db.session import get_db
from app.models.enums import UserRole
from app.models.user import User

bearer_scheme = HTTPBearer(auto_error=False)

CREDENTIALS_ERROR = HTTPException(
    status_code=status.HTTP_401_UNAUTHORIZED,
    detail="Could not validate credentials",
    headers={"WWW-Authenticate": "Bearer"},
)


def get_current_user(
    credentials: HTTPAuthorizationCredentials | None = Depends(bearer_scheme),
    db: Session = Depends(get_db),
) -> User:
    if credentials is None:
        raise CREDENTIALS_ERROR
    try:
        payload = decode_token(credentials.credentials)
    except jwt.ExpiredSignatureError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Token has expired",
            headers={"WWW-Authenticate": "Bearer"},
        ) from None
    except jwt.PyJWTError:
        raise CREDENTIALS_ERROR from None

    if payload.get("type") != "access":
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Refresh token cannot be used for authentication",
        )

    try:
        user_id = int(payload["sub"])
    except (KeyError, TypeError, ValueError):
        raise CREDENTIALS_ERROR from None

    user = db.scalar(select(User).where(User.id == user_id, User.is_deleted.is_(False)))
    if user is None:
        raise CREDENTIALS_ERROR
    if not user.is_active:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User account is disabled")
    return user


def require_roles(*roles: UserRole) -> Callable[..., User]:
    """Dependency factory: allow only the listed roles.

    Admin and system developer always pass — the latter is a superuser that
    must never be locked out by role gates.
    """
    allowed: set[UserRole] = set(roles) | {UserRole.ADMIN, UserRole.SYSTEM_DEVELOPER}

    def dependency(current_user: User = Depends(get_current_user)) -> User:
        if current_user.role not in allowed:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail=f"Role '{current_user.role.value}' is not permitted to perform this action",
            )
        return current_user

    return dependency


# --- Named guards, by capability rather than by role list ---

#: Any authenticated user, including read-only. Use for GET endpoints.
CurrentUser = Depends(get_current_user)

#: Create / update business records — everyone except read-only.
require_write = require_roles(UserRole.MANAGER, UserRole.FIELD_STAFF)

#: Approve, reject, delete, reassign — managers and admins.
require_manager = require_roles(UserRole.MANAGER)

#: User administration, reference data — admins only.
require_admin = require_roles()


def client_ip(request: Request) -> str | None:
    """Real client IP, honouring the nginx X-Forwarded-For header."""
    forwarded = request.headers.get("x-forwarded-for")
    if forwarded:
        return forwarded.split(",")[0].strip()
    return request.client.host if request.client else None


def roles_csv(roles: Iterable[UserRole]) -> str:
    return ", ".join(r.value for r in roles)
