"""Read-only query selectors for the IAM app."""

from __future__ import annotations

from django.db.models import QuerySet

from simorgh.apps.iam.models import Permission, Role

__all__ = [
    "get_role_by_code",
    "get_role_by_id",
    "list_roles_for_tenant",
    "get_permissions_for_role",
    "list_permissions",
]


def get_role_by_code(tenant_id: int, code: str) -> Role | None:
    """Return the Role matching (tenant_id, code), or the global role if no tenant match.

    Checks tenant-specific roles first; falls back to global (tenant=None) system roles.
    Returns ``None`` when no match is found.
    """
    role = Role.objects.filter(tenant_id=tenant_id, code=code).first()
    if role is None:
        role = Role.objects.filter(tenant__isnull=True, code=code).first()
    return role


def get_role_by_id(role_id: int, tenant_id: int | None = None) -> Role:
    """Return the Role with the given PK, optionally scoped to a tenant.

    Raises ``Role.DoesNotExist`` when not found.
    """
    qs = Role.objects.filter(pk=role_id)
    if tenant_id is not None:
        qs = qs.filter(tenant_id=tenant_id)
    return qs.get()


def list_roles_for_tenant(tenant_id: int, *, include_global: bool = True) -> QuerySet:
    """Return all Roles available for a tenant.

    When ``include_global=True`` (default), also returns system roles where
    ``tenant`` is NULL — those are available to every tenant.
    """
    if include_global:
        return Role.objects.filter(
            tenant_id__in=[tenant_id, None]
        ).order_by("tenant_id", "code")
    return Role.objects.filter(tenant_id=tenant_id).order_by("code")


def get_permissions_for_role(role: Role) -> list[str]:
    """Return the resolved flat list of permission codenames for a role.

    Resolved permissions = own permissions UNION parent's resolved permissions
    (recursive, up to 10 levels deep as a safety guard against infinite loops).
    """
    seen_ids: set[int] = set()
    codenames: set[str] = set()

    def _collect(r: Role, depth: int = 0) -> None:
        if depth > 10 or r.pk in seen_ids:
            return
        seen_ids.add(r.pk)
        codenames.update(r.permissions.values_list("codename", flat=True))
        if r.parent_role_id:
            try:
                _collect(r.parent_role, depth + 1)
            except Role.DoesNotExist:
                pass

    # Prefetch to avoid N+1 inside recursion.
    role_with_perms = (
        Role.objects.prefetch_related("permissions")
        .select_related("parent_role__parent_role")
        .get(pk=role.pk)
    )
    _collect(role_with_perms)
    return sorted(codenames)


def list_permissions(*, module: str | None = None) -> QuerySet:
    """Return all registered Permissions, optionally filtered by module."""
    qs = Permission.objects.all()
    if module:
        qs = qs.filter(module=module)
    return qs.order_by("codename")
