"""
Enforcement layer for the dynamic RBAC.

Provides one source of truth for "can this staff member do X":

    user_has_permission(staff_profile, codename)      -> bool
    get_effective_permission_codenames(staff_profile) -> set[str]

plus DRF glue for the function-based views used throughout the project:

    @requires_permission('procurement.purchase_order.add')
    def create_purchase_order(request): ...

and a class-based variant for APIView/ViewSet:

    permission_classes = [HasPermission('procurement.purchase_order.add')]

Coexistence guarantee: system administrators and super admins implicitly hold
every permission, so wiring a view to RBAC never locks out an admin even before
roles are configured. ``check_if_system_administrator`` remains the legacy
escape hatch and is honoured here.
"""
from functools import wraps

from rest_framework.permissions import BasePermission
from rest_framework.response import Response

from human_resource.models import StaffProfile
from system_administration.utils import check_if_system_administrator

from .models import StaffRole


def get_staff_profile(user):
    """Resolve the StaffProfile for an authenticated user, or None."""
    if user is None or not getattr(user, 'is_authenticated', False):
        return None
    profile = getattr(user, 'user_staff_profile', None)
    if profile is not None:
        return profile
    try:
        return StaffProfile.objects.get(user=user)
    except StaffProfile.DoesNotExist:
        return None


def is_unrestricted(staff_profile):
    """True for super admins / system administrators (implicit *all* access)."""
    if staff_profile is None:
        return False
    if getattr(staff_profile, 'is_super_admin', False):
        return True
    return check_if_system_administrator(staff_profile)


def _assignment_active(staff_role):
    """Honour branch scoping on a StaffRole assignment."""
    if not staff_role.is_active:
        return False
    if staff_role.main_branch_only:
        branch = staff_role.staff_profile.company_branch
        if branch is None or not branch.main_branch:
            return False
    return True


def get_effective_permission_codenames(staff_profile):
    """Union of permission codenames across the staff member's active roles.

    Returns the sentinel ``{'*'}`` for unrestricted (super admin) users.
    """
    if staff_profile is None:
        return set()
    if is_unrestricted(staff_profile):
        return {'*'}

    assignments = (
        StaffRole.objects
        .filter(staff_profile=staff_profile, is_active=True,
                role__recycle_bin=False)
        .select_related('staff_profile__company_branch')
        .prefetch_related('role__permissions')
    )
    codenames = set()
    for sr in assignments:
        if not _assignment_active(sr):
            continue
        for perm in sr.role.permissions.all():
            if perm.is_active:
                codenames.add(perm.codename)
    return codenames


def user_has_permission(staff_profile, codename):
    """Does this staff member hold ``codename`` (or implicit *all* access)?"""
    if staff_profile is None:
        return False
    if is_unrestricted(staff_profile):
        return True
    return codename in get_effective_permission_codenames(staff_profile)


def user_has_any(staff_profile, codenames):
    if is_unrestricted(staff_profile):
        return True
    effective = get_effective_permission_codenames(staff_profile)
    return any(c in effective for c in codenames)


def user_can_at_branch(staff_profile, codename, require_main_branch=None):
    """Permission check that also honours a legacy main-branch business rule.

    ``require_main_branch=True``  -> action only allowed from the company main branch.
    ``require_main_branch=False`` -> action only allowed from a non-main branch.
    ``require_main_branch=None``  -> no branch constraint (same as user_has_permission).

    Unrestricted users (super admins / system admins) bypass the branch rule,
    matching the old ``... or check_if_system_administrator(...)`` short-circuit.
    """
    if not user_has_permission(staff_profile, codename):
        return False
    if require_main_branch is None or is_unrestricted(staff_profile):
        return True
    branch = getattr(staff_profile, 'company_branch', None)
    is_main = bool(branch and branch.main_branch)
    return is_main if require_main_branch else (branch is not None and not is_main)


# --- DRF integration -------------------------------------------------------

def HasPermission(*codenames):
    """Factory returning a DRF permission class requiring ALL given codenames.

    Usage: ``permission_classes = [IsAuthenticated, HasPermission('x.y.add')]``
    """
    class _HasPermission(BasePermission):
        message = 'You do not have permission to perform this action.'

        def has_permission(self, request, view):
            staff_profile = get_staff_profile(request.user)
            if staff_profile is None:
                return False
            if is_unrestricted(staff_profile):
                return True
            effective = get_effective_permission_codenames(staff_profile)
            return all(c in effective for c in codenames)

    return _HasPermission


def requires_permission(*codenames, require='all'):
    """Decorator for function-based DRF views.

    ``require='all'`` (default) needs every codename; ``require='any'`` needs
    at least one. Assumes the view is already authenticated (IsAuthenticated).
    """
    def decorator(view_func):
        @wraps(view_func)
        def _wrapped(request, *args, **kwargs):
            staff_profile = get_staff_profile(request.user)
            if staff_profile is None:
                return Response(
                    {'message': 'Authentication required.', 'payload': {}},
                    status=401,
                )
            if not is_unrestricted(staff_profile):
                effective = get_effective_permission_codenames(staff_profile)
                ok = (
                    all(c in effective for c in codenames)
                    if require == 'all'
                    else any(c in effective for c in codenames)
                )
                if not ok:
                    return Response(
                        {
                            'message': 'You do not have permission to perform this action.',
                            'payload': {'required_permissions': list(codenames)},
                        },
                        status=403,
                    )
            return view_func(request, *args, **kwargs)
        return _wrapped
    return decorator
