"""Access-grant signals for workspace membership materialization, and workspace seeding.

Access grants
-------------
When a user is added to a :class:`~simorgh.apps.memberships.models.Membership`
(the tenant-scoped role assignment) the signal handler checks whether any
workspace in that tenant has declared a
:class:`~simorgh.apps.workspaces.models.WorkspaceAccessGrant` for that role
and, if so, materializes a ``WorkspaceMembership`` record.

This is a convenience materialization — conceptually, a user *may* access
any workspace for which at least one of their roles has an access grant.
The ``WorkspaceMembership`` table exists for query performance.

Workspace seeding
-----------------
When a new :class:`~simorgh.apps.tenants.models.Tenant` is created the
``_on_tenant_created`` handler calls
:func:`~simorgh.apps.workspaces.workspace_seeds.seed_workspaces_for_tenant`
to provision the canonical workspace catalogue (administration, management,
document-management, support-center) together with their default roles and
access grants.
"""

from __future__ import annotations

import logging

from django.db.models.signals import m2m_changed
from django.dispatch import receiver

logger = logging.getLogger(__name__)


def _materialize_access(user, role, tenant) -> None:
    """Materialize WorkspaceMembership for every active workspace in *tenant*
    that has a ``WorkspaceAccessGrant`` for *role*."""
    from simorgh.apps.workspaces.models import WorkspaceAccessGrant  # noqa: PLC0415
    from simorgh.apps.workspaces.services import add_member  # noqa: PLC0415

    grants = (
        WorkspaceAccessGrant.objects.filter(
            workspace__tenant=tenant,
            workspace__is_active=True,
            role=role,
        )
        .select_related("workspace")
    )
    for grant in grants:
        try:
            add_member(grant.workspace, user)
        except Exception:  # noqa: BLE001
            logger.exception(
                "workspace access materialization failed: ws=%s user=%s role=%s",
                grant.workspace_id,
                user.pk,
                role.pk,
            )


def register_signals() -> None:
    """Connect the Membership.users m2m signal.

    Called once from :meth:`WorkspacesConfig.ready`.
    """
    from simorgh.apps.memberships.models import Membership  # noqa: PLC0415

    def on_membership_users_changed(sender, instance, action, pk_set, **kwargs):
        """``instance`` is a Membership; ``pk_set`` is the set of new user PKs."""
        if action != "post_add" or not pk_set:
            return
        if not instance.is_active:
            return

        from django.contrib.auth import get_user_model  # noqa: PLC0415

        User = get_user_model()
        role = instance.role
        tenant = instance.tenant
        for user_id in pk_set:
            try:
                user = User.objects.get(pk=user_id)
            except User.DoesNotExist:
                continue
            _materialize_access(user, role, tenant)

    m2m_changed.connect(
        on_membership_users_changed,
        sender=Membership.users.through,
        weak=False,
        dispatch_uid="workspaces.auto_enroll_on_membership_users_changed",
    )


def register_tenant_seed_signal() -> None:
    """Seed canonical workspaces when a new Tenant is saved.

    Called once from :meth:`WorkspacesConfig.ready`.
    Mirrors the pattern used by :mod:`simorgh.apps.helpdesk.apps`.
    """
    from django.db.models.signals import post_save  # noqa: PLC0415

    from simorgh.apps.tenants.models import Tenant  # noqa: PLC0415

    def _on_tenant_created(sender, instance, created, **kwargs):
        if not created:
            return
        try:
            from simorgh.apps.workspaces.workspace_seeds import seed_workspaces_for_tenant  # noqa: PLC0415
            seed_workspaces_for_tenant(instance)
        except Exception:  # noqa: BLE001
            logger.exception(
                "workspace auto-seed failed for new tenant %s",
                getattr(instance, "slug", instance.pk),
            )

    post_save.connect(
        _on_tenant_created,
        sender=Tenant,
        weak=False,
        dispatch_uid="workspaces.seed_on_tenant_created",
    )
