"""Recruiter-scoped access rules for candidate data.

Access requires BOTH of the following — holding a candidate permission is not
by itself enough to see a candidate:

1. The relevant Django permission (candidates.view_candidate, .change_candidate,
   …). Enforced by IsCandidateOwnerOrStaff.has_permission.
2. A connection to the candidate: the candidate applied to a JD assigned to
   this recruiter. Enforced by the helpers in this module.

A candidate with no application to one of the recruiter's assigned JDs is
invisible to that recruiter everywhere — list, detail, search, counts, exports —
including candidates who have not applied to anything at all.

Managers and admins are unrestricted; candidates only ever see themselves.

Both helpers here are driven by the same predicate so the list endpoint and the
detail endpoint can never disagree about who is visible.
"""

from django.db.models import Q


def is_privileged(user):
    """True for admins and any *_MANAGER role — unrestricted candidate access.

    Mirrors apps.jobs.views._is_manager so JD scoping and candidate scoping
    apply the same definition of "manager".
    """
    if not (user and user.is_authenticated):
        return False
    role = (getattr(user, "role", "") or "").upper()
    return role == "ADMIN" or "MANAGER" in role


def recruiter_candidate_filter(user):
    """Q object selecting the candidates `user` (a recruiter) may access.

    Strictly: the candidate must have applied to a JD this recruiter is
    assigned to. Candidates with no applications, and candidates who only
    applied to other recruiters' JDs, are both excluded.
    """
    return Q(applications__job__recruiter_assignments__recruiter=user)


def scope_candidates(queryset, user):
    """Restrict a Candidate queryset to what `user` is allowed to see."""
    if is_privileged(user):
        return queryset
    role = (getattr(user, "role", "") or "").upper()
    if role == "CANDIDATE":
        return queryset.filter(user=user)
    return queryset.filter(recruiter_candidate_filter(user)).distinct()


def can_view_candidate(user, candidate):
    """Authoritative check for access to one candidate's details.

    Deliberately re-queries rather than trusting anything supplied by the
    caller, so a manipulated id/param can't influence the outcome.
    """
    if not (user and user.is_authenticated):
        return False
    if is_privileged(user):
        return True

    role = (getattr(user, "role", "") or "").upper()
    if role == "CANDIDATE":
        return getattr(candidate, "user_id", None) == user.id

    # Own profile is always visible, whatever the role.
    if getattr(candidate, "user_id", None) == user.id:
        return True

    from apps.pipeline.models import JobApplication

    # The candidate must have applied to a JD assigned to this recruiter.
    # No application to one of their JDs -> no access, full stop.
    return JobApplication.objects.filter(
        candidate=candidate,
        job__recruiter_assignments__recruiter=user,
    ).exists()


# Message shown to a recruiter who reaches a candidate outside their scope.
# Kept here so the API and the UI use identical wording.
CANDIDATE_FORBIDDEN_MESSAGE = (
    "You are not authorized to view this candidate because this application "
    "does not belong to one of your assigned Job Descriptions."
)
