"""Pure access-policy engine (no Django imports).

Effective permissions of a user inside one organization are computed from:
  layer 1  module enablement        (permissions of disabled modules vanish)
  layer 2  organization/membership  (inactive => nothing)
  layer 3+4 role grants and per-user overrides
  layer 5  data scope               (how much data a permission reaches)

Rules: deny-by-default; an explicit DENY override removes the permission
completely and always beats any ALLOW (role grant or allow override).
"""
from __future__ import annotations

from dataclasses import dataclass
from typing import Iterable, Mapping, Optional

CORE_MODULE = "core"

SCOPE_ORDER: dict[str, int] = {
    "self": 10,
    "team": 20,
    "unit": 30,
    "organization": 40,
    "assigned_organizations": 50,
    "all": 60,
}


def scope_rank(scope: str) -> int:
    try:
        return SCOPE_ORDER[scope]
    except KeyError:
        raise ValueError(f"unknown data scope: {scope!r}")


@dataclass(frozen=True)
class RoleGrant:
    permission: str
    scope: str
    can_delegate: bool = False


@dataclass(frozen=True)
class OverrideRule:
    permission: str
    effect: str  # "allow" | "deny"
    scope: str = "self"
    can_delegate: bool = False


@dataclass(frozen=True)
class Grant:
    scope: str
    can_delegate: bool


class EffectiveAccess:
    """Result of the computation. Immutable view over {permission: Grant}."""

    def __init__(self, grants: Mapping[str, Grant]):
        self._grants = dict(grants)

    @property
    def permissions(self) -> dict[str, Grant]:
        return dict(self._grants)

    def has(self, permission: str, min_scope: Optional[str] = None) -> bool:
        grant = self._grants.get(permission)
        if grant is None:
            return False
        if min_scope is None:
            return True
        return scope_rank(grant.scope) >= scope_rank(min_scope)

    def scope_of(self, permission: str) -> Optional[str]:
        grant = self._grants.get(permission)
        return grant.scope if grant else None

    def can_delegate(self, permission: str, scope: str) -> bool:
        """May the holder hand this permission (at most at `scope`) to others?"""
        grant = self._grants.get(permission)
        return bool(grant and grant.can_delegate and scope_rank(grant.scope) >= scope_rank(scope))


EMPTY_ACCESS = EffectiveAccess({})


def merge_effective_access(*accesses: EffectiveAccess) -> EffectiveAccess:
    """Combine independently computed accesses (e.g. an organization membership and the
    user's NoTeam staff role). Additive: widest scope wins, delegation is OR-ed. A deny
    override only removes the permission from the source it was written for."""
    merged: dict[str, Grant] = {}
    for access in accesses:
        for code, grant in access.permissions.items():
            current = merged.get(code)
            if current is None:
                merged[code] = grant
            else:
                best = grant.scope if scope_rank(grant.scope) > scope_rank(current.scope) else current.scope
                merged[code] = Grant(best, current.can_delegate or grant.can_delegate)
    return EffectiveAccess(merged) if merged else EMPTY_ACCESS


def compute_effective_access(
    *,
    organization_active: bool,
    membership_active: bool,
    enabled_modules: Iterable[str],
    permission_modules: Mapping[str, str],
    role_grants: Iterable[RoleGrant],
    overrides: Iterable[OverrideRule],
    blocked_permissions: Iterable[str] = (),
) -> EffectiveAccess:
    if not organization_active or not membership_active:
        return EMPTY_ACCESS

    modules = set(enabled_modules) | {CORE_MODULE}
    grants: dict[str, Grant] = {}

    def add(permission: str, scope: str, can_delegate: bool) -> None:
        current = grants.get(permission)
        if current is None:
            grants[permission] = Grant(scope, can_delegate)
            return
        best_scope = scope if scope_rank(scope) > scope_rank(current.scope) else current.scope
        grants[permission] = Grant(best_scope, current.can_delegate or can_delegate)

    for grant in role_grants:
        add(grant.permission, grant.scope, grant.can_delegate)

    denied: set[str] = set(blocked_permissions)
    for rule in overrides:
        if rule.effect == "deny":
            denied.add(rule.permission)
        elif rule.effect == "allow":
            add(rule.permission, rule.scope, rule.can_delegate)
        else:
            raise ValueError(f"unknown override effect: {rule.effect!r}")

    result = {
        code: grant
        for code, grant in grants.items()
        if code not in denied and permission_modules.get(code) in modules
    }
    return EffectiveAccess(result)
