"""DRF permissions and queryset helpers for the v1 API.

Programme scoping is derived from the existing FK trio on
:class:`apps.rims.grants.models.FundingRecord` — ``grant_manager``,
``program_lead``, and ``finance_officer``. There is no separate
``ProgrammeMembership`` model; assigning a user to one of those FKs is the
single source of truth for which programmes they can see in the API.

The helpers here are deliberately small and composable so the same scope
predicates power the regression tests and the live viewsets.
"""
from __future__ import annotations

from django.db.models import Q, QuerySet

from apps.core.permissions.roles import UserRole

# Roles that are allowed to act on RIMS data without per-FK staff assignment.
# Admins always see everything. Grants managers and program managers see
# everything tied to their assigned funding records (enforced via Q-filter).
_GLOBAL_RIMS_ROLES = frozenset(
    {
        UserRole.ADMIN.value,
        UserRole.SYSTEM_ADMIN.value,
    }
)

_GRANTS_STAFF_ROLES = frozenset(
    {
        UserRole.GRANTS_MANAGER.value,
        UserRole.PROGRAM_MANAGER.value,
        UserRole.PROGRAM_DIRECTOR.value,
        UserRole.PROGRAMME_OFFICER.value,
    }
)

_FINANCE_STAFF_ROLES = frozenset(
    {
        UserRole.FINANCE_OFFICER.value,
        UserRole.FINANCE_DIRECTOR.value,
    }
)


def _user_role(user) -> str:
    return getattr(user, "role", "") or ""


def _is_global_rims_user(user) -> bool:
    if not user or not user.is_authenticated:
        return False
    if user.is_superuser:
        return True
    return _user_role(user) in _GLOBAL_RIMS_ROLES


def application_scope_q(user, *, prefix: str = "") -> Q:
    """Return a Q expression filtering ``Application`` rows visible to ``user``.

    ``prefix`` lets callers reuse the predicate against related models — e.g.
    ``application_scope_q(user, prefix="application__")`` filters
    ``InterviewSchedule`` / ``VerificationRecord`` / ``Award`` querysets via
    their ``application`` FK.
    """
    if not user or not user.is_authenticated:
        return Q(pk__in=[])
    if _is_global_rims_user(user):
        return Q()

    role = _user_role(user)
    p = prefix
    clauses = [Q(**{f"{p}applicant": user})]

    if role in _GRANTS_STAFF_ROLES:
        clauses.append(Q(**{f"{p}call__funding_record__grant_manager": user}))
        clauses.append(Q(**{f"{p}call__funding_record__program_lead": user}))

    if role in _FINANCE_STAFF_ROLES:
        # Finance staff see applications tied to funding records they manage.
        # In practice this is mainly relevant for Award rows, but we keep the
        # predicate consistent for all application-scoped models.
        clauses.append(Q(**{f"{p}call__funding_record__finance_officer": user}))

    q = clauses[0]
    for clause in clauses[1:]:
        q |= clause
    return q


def scope_application_qs(user, qs: QuerySet) -> QuerySet:
    """Filter an ``Application`` queryset to rows the user is permitted to see."""
    return qs.filter(application_scope_q(user)).distinct()


def scope_award_qs(user, qs: QuerySet) -> QuerySet:
    """Filter an ``Award`` queryset via its ``application`` FK."""
    return qs.filter(application_scope_q(user, prefix="application__")).distinct()


def scope_interview_qs(user, qs: QuerySet) -> QuerySet:
    """Filter an ``InterviewSchedule`` queryset via its ``application`` FK."""
    return qs.filter(application_scope_q(user, prefix="application__")).distinct()


def scope_verification_qs(user, qs: QuerySet) -> QuerySet:
    """Filter a ``VerificationRecord`` queryset via its ``application`` FK."""
    return qs.filter(application_scope_q(user, prefix="application__")).distinct()
