"""Object-level permissions and visibility helpers (PRD REPO-SP4)."""

from __future__ import annotations

from django.contrib.auth.models import AnonymousUser, Group

from apps.core.permissions.policies import grant_object_perm
from apps.core.permissions.roles import UserRole


# Canonical set of roles that curate/administer the Repository. This is the
# single source of truth reused by ``_is_internal_user`` here, by
# ``RepoManagerRequiredMixin`` in documents/views.py, and by the analytics
# manager mixin — keeping visibility, management, and analytics access aligned.
# KMO == SRS "Collection Manager".
REPOSITORY_MANAGEMENT_ROLES = frozenset(
    {
        UserRole.ADMIN,
        UserRole.SYSTEM_ADMIN,
        UserRole.REPO_MANAGER,
        UserRole.KHUB_MANAGER,
        UserRole.REPOSITORY_ADMIN,
        UserRole.EDITOR_IN_CHIEF,
        UserRole.KNOWLEDGE_MANAGEMENT_OFFICER,
    }
)

# SP6 two-tier review (2026-07 field interviews): Reviewers score the rubric
# and recommend, Publication Assistants record compliance checks — neither may
# take the final publish/reject decision, which stays with the management tier
# above. This wider set only opens the QA queue/review pages; per-role action
# gating happens in the QA views.
QA_PARTICIPANT_ROLES = REPOSITORY_MANAGEMENT_ROLES | {
    UserRole.REVIEWER,
    UserRole.PUBLICATION_ASSISTANT,
}

# SP5 withdrawal authority (2026-07 field interviews): removal/withdrawal is
# the Knowledge Hub Manager's call (with Editor in Chief per interview §4.3
# and the admin tier). Deliberately narrower than the management set — the
# KMO / Collection Manager curates versions and archival but does not
# withdraw published documents.
WITHDRAWAL_AUTHORITY_ROLES = frozenset(
    {
        UserRole.ADMIN,
        UserRole.SYSTEM_ADMIN,
        UserRole.REPO_MANAGER,
        UserRole.REPOSITORY_ADMIN,
        UserRole.KHUB_MANAGER,
        UserRole.EDITOR_IN_CHIEF,
    }
)


def is_open_access(document) -> bool:
    return document.effective_visibility == document.Visibility.PUBLIC_OPEN


def can_access(user, document) -> bool:
    """Decide whether ``user`` can view metadata + open the detail page."""
    visibility = document.effective_visibility
    if visibility == document.Visibility.PUBLIC_OPEN:
        return True
    if visibility == document.Visibility.INTERNAL:
        return _is_internal_user(user)
    if not user or isinstance(user, AnonymousUser) or not user.is_authenticated:
        return False
    if user.is_superuser or _is_internal_user(user):
        return True
    if visibility == document.Visibility.AUTHENTICATED:
        return True
    # Restricted — guardian per-document grant or owner.
    if document.submitted_by_id == user.pk:
        return True
    return user.has_perm("repository_documents.view_document", document) or user.has_perm(
        "repository_documents.download_document", document
    )


def can_download(user, document) -> bool:
    if can_access(user, document) is False:
        return False
    if user and user.is_authenticated and user.is_superuser:
        return True
    if user and user.is_authenticated and _is_internal_user(user):
        return True
    if is_open_access(document):
        return True
    if user and user.is_authenticated:
        return user.has_perm("repository_documents.download_document", document)
    return False


def _is_internal_user(user) -> bool:
    if not user or not getattr(user, "is_authenticated", False):
        return False
    return getattr(user, "role", "") in REPOSITORY_MANAGEMENT_ROLES


def apply_collection_visibility(document) -> None:
    """Materialise the document's visibility on publish.

    For RESTRICTED collections we grant the ``view`` and ``download`` perms
    only to the collection managers + submitter; everyone else must request
    access. PUBLIC_OPEN and AUTHENTICATED rely on queryset filtering and
    do not need per-object grants.
    """
    visibility = document.effective_visibility
    if visibility in (document.Visibility.PUBLIC_OPEN, document.Visibility.AUTHENTICATED):
        return

    # Explicit grants for RESTRICTED / INTERNAL — collection managers + submitter.
    grantees = list(document.collection.managed_by.filter(is_active=True))
    if document.submitted_by_id and document.submitted_by not in grantees:
        grantees.append(document.submitted_by)
    for user in grantees:
        grant_object_perm(user, "repository_documents.view_document", document)
        grant_object_perm(user, "repository_documents.download_document", document)
