"""Effective-access cache keys and invalidation (RBAC v2).

One owner for key generation and busting. Keys vary by (user, organization).
Invalidate on: role capability edits, user role assignment, manager
assignment, scope grant, account status, organization membership, and
organization role changes. The legacy v1 cache (User.rbac_cache_key) is
busted alongside during the dual-read migration so both resolvers stay
consistent.
"""

from django.core.cache import cache

CACHE_TTL = 60  # seconds; mutations also invalidate explicitly


def cache_key_part(value) -> str:
    """Normalize a pk for cache keys. UUID pks arrive in two shapes — the
    model attribute (custom uuid7, str() without dashes) and values_list()
    rows (plain uuid.UUID, str() with dashes). f-stringing them raw produces
    different keys for the same row, silently breaking invalidation, so
    always go through .hex."""
    hex_value = getattr(value, "hex", None)
    return hex_value if isinstance(hex_value, str) else str(value)


def effective_access_key(user_id, organization_id) -> str:
    return f"rbacv2:caps:{cache_key_part(user_id)}:{cache_key_part(organization_id)}"


def invalidate_user(user_id, organization_id) -> None:
    from apps.core.models import User

    cache.delete_many([
        effective_access_key(user_id, organization_id),
        User.rbac_cache_key(user_id, organization_id),
    ])


def invalidate_pairs(pairs) -> None:
    """Bust (user_id, organization_id) pairs for both v1 and v2 caches."""
    from apps.core.models import User

    keys = []
    for user_id, organization_id in pairs:
        keys.append(effective_access_key(user_id, organization_id))
        keys.append(User.rbac_cache_key(user_id, organization_id))
    if keys:
        cache.delete_many(keys)


def invalidate_role(role) -> None:
    """Bust every (user, org) pair holding this role."""
    invalidate_pairs(role.user_assignments.values_list("user_id", "organization_id"))
