"""Closed permission vocabulary for project authorization (design §3.2, §6).

Roles are bundles of these strings. Core owns the list: codegen and API callers may
compose roles from it but cannot invent permission strings.
"""

from __future__ import annotations

from collections.abc import Iterable

from src.core.exceptions import ValidationError

MEMBER_READ = "member.read"
MEMBER_INVITE = "member.invite"
MEMBER_SUSPEND = "member.suspend"
MEMBER_REINSTATE = "member.reinstate"
MEMBER_SUSPEND_ADMIN = "member.suspend_admin"
MEMBER_REINSTATE_ADMIN = "member.reinstate_admin"
MEMBER_REINSTATE_LEGACY = "member.reinstate_legacy"
ROLE_GRANT_ADMIN = "member.role.grant_admin"
ROLE_REVOKE_ADMIN = "member.role.revoke_admin"
ROLE_GRANT_STAFF = "member.role.grant_staff"
ROLE_REVOKE_STAFF = "member.role.revoke_staff"
ROLE_GRANT_MEMBER = "member.role.grant_member"
ROLE_REVOKE_MEMBER = "member.role.revoke_member"
ROLE_GRANT_OWNER = "member.role.grant_owner"
ROLE_REVOKE_OWNER = "member.role.revoke_owner"
OWNER_TRANSFER = "member.owner.transfer"
STAFF = "staff"
BILLING = "billing"

# Legacy compatibility only (design §6). Deprecated as a permission: the owner is
# identified by is_owner. Accepted solely on codegen-created system roles during migration.
LEGACY_SUPER_ADMIN = "super_admin"

VOCABULARY: frozenset[str] = frozenset({
    MEMBER_READ,
    MEMBER_INVITE,
    MEMBER_SUSPEND,
    MEMBER_REINSTATE,
    MEMBER_SUSPEND_ADMIN,
    MEMBER_REINSTATE_ADMIN,
    MEMBER_REINSTATE_LEGACY,
    ROLE_GRANT_ADMIN,
    ROLE_REVOKE_ADMIN,
    ROLE_GRANT_STAFF,
    ROLE_REVOKE_STAFF,
    ROLE_GRANT_MEMBER,
    ROLE_REVOKE_MEMBER,
    ROLE_GRANT_OWNER,
    ROLE_REVOKE_OWNER,
    OWNER_TRANSFER,
    STAFF,
    BILLING,
})

# Never assignable to any role. Ownership changes only through the owner-transfer flow.
NEVER_ROLE_ASSIGNABLE: frozenset[str] = frozenset({ROLE_GRANT_OWNER, ROLE_REVOKE_OWNER})

# Assignable only to protected system roles (the owner preset).
PROTECTED_ONLY: frozenset[str] = frozenset({OWNER_TRANSFER})

LEGACY_ONLY: frozenset[str] = frozenset({LEGACY_SUPER_ADMIN})

ACCEPTED_NAMES: frozenset[str] = VOCABULARY | LEGACY_ONLY

OWNER_PRESET: tuple[str, ...] = (
    ROLE_GRANT_ADMIN, ROLE_REVOKE_ADMIN, ROLE_GRANT_STAFF, ROLE_REVOKE_STAFF,
    ROLE_GRANT_MEMBER, ROLE_REVOKE_MEMBER,
    MEMBER_SUSPEND, MEMBER_REINSTATE, MEMBER_SUSPEND_ADMIN, MEMBER_REINSTATE_ADMIN,
    MEMBER_REINSTATE_LEGACY,
    MEMBER_READ, MEMBER_INVITE, OWNER_TRANSFER, STAFF, BILLING,
)
ADMIN_PRESET: tuple[str, ...] = (
    ROLE_GRANT_ADMIN, ROLE_REVOKE_ADMIN, ROLE_GRANT_STAFF, ROLE_REVOKE_STAFF,
    ROLE_GRANT_MEMBER, ROLE_REVOKE_MEMBER,
    MEMBER_SUSPEND, MEMBER_REINSTATE, MEMBER_READ, MEMBER_INVITE, STAFF,
)
STAFF_PRESET: tuple[str, ...] = (STAFF,)
MEMBER_PRESET: tuple[str, ...] = ()


def validate_role_permissions(
    permissions: Iterable[str],
    *,
    protected: bool,
    system_created: bool,
) -> list[str]:
    """Return the deduplicated permission list, or raise ValidationError (422).

    protected: the role being defined is a protected system role (owner).
    system_created: the role is codegen/system-created (not an API user role).
    """
    seen: list[str] = []
    unknown: list[str] = []
    for perm in permissions:
        if perm not in ACCEPTED_NAMES:
            unknown.append(perm)
        elif perm not in seen:
            seen.append(perm)
    if unknown:
        raise ValidationError(
            f"Unknown permission(s): {', '.join(sorted(set(unknown)))}",
            error="unknown_permission",
        )
    never = [p for p in seen if p in NEVER_ROLE_ASSIGNABLE]
    if never:
        raise ValidationError(
            f"Permission(s) cannot be assigned to any role: {', '.join(never)}",
            error="permission_not_assignable",
        )
    protected_only = [p for p in seen if p in PROTECTED_ONLY]
    if protected_only and not protected:
        raise ValidationError(
            f"Permission(s) allowed only on the protected owner role: {', '.join(protected_only)}",
            error="permission_protected_only",
        )
    legacy = [p for p in seen if p in LEGACY_ONLY]
    if legacy and not system_created:
        raise ValidationError(
            f"Deprecated permission(s) not allowed on user roles: {', '.join(legacy)}",
            error="permission_deprecated",
        )
    return seen
