"""Tenant and workspace scoping context managers for fixture execution.

Provides `tenant_scope` and `workspace_scope` context managers that set up
the proper `RequestContext` during data provisioning operations.
"""

from __future__ import annotations

import contextlib
from typing import TYPE_CHECKING, Any

from simorgh.core.context import RequestContext, reset_request_context, set_request_context

if TYPE_CHECKING:
    from collections.abc import Iterator

    from simorgh.apps.accounts.models import User
    from simorgh.apps.organizations.models import OrganizationNode
    from simorgh.apps.tenants.models import Tenant
    from simorgh.apps.workspaces.models import Workspace


@contextlib.contextmanager
def tenant_scope(
    tenant: Tenant,
    actor: User | None = None,
    org_node: OrganizationNode | None = None,
    extra: dict[str, Any] | None = None,
) -> Iterator[RequestContext]:
    """Set up a RequestContext scoped to a specific tenant for fixture execution.

    Usage:
        tenant = Tenant.objects.get(slug="solan")
        with tenant_scope(tenant) as ctx:
            # All tenant-scoped operations within this block use ctx
            providers = [MyProvider()]
            runner.execute(providers, context={"tenant": tenant, "ctx": ctx})
    """
    org_node_ids: frozenset[int] = frozenset()
    if org_node is not None:
        from simorgh.apps.organizations.services import descendants_of

        descendant_ids = descendants_of(org_node.pk).values_list("pk", flat=True)
        org_node_ids = frozenset([org_node.pk, *descendant_ids])

    ctx = RequestContext(
        actor=actor or None,
        tenant=tenant,
        org_node_ids=org_node_ids,
        memberships=(),
        permissions=frozenset(),
        workspace=None,
        extra=extra or {},
    )
    token = set_request_context(ctx)
    try:
        yield ctx
    finally:
        reset_request_context(token)


@contextlib.contextmanager
def workspace_scope(
    workspace: Workspace,
    tenant: Tenant | None = None,
    actor: User | None = None,
    extra: dict[str, Any] | None = None,
) -> Iterator[RequestContext]:
    """Set up a RequestContext scoped to a specific workspace for fixture execution.

    Usage:
        workspace = Workspace.objects.get(slug="main-workspace")
        with workspace_scope(workspace) as ctx:
            providers = [WorkspaceDataProvider()]
            runner.execute(providers, context={"workspace": workspace, "tenant": workspace.tenant, "ctx": ctx})
    """
    ctx = RequestContext(
        actor=actor or None,
        tenant=tenant or workspace.tenant,
        memberships=(),
        permissions=frozenset(),
        workspace=workspace,
        extra=extra or {},
    )
    token = set_request_context(ctx)
    try:
        yield ctx
    finally:
        reset_request_context(token)


@contextlib.contextmanager
def system_scope(
    actor: User | None = None,
    extra: dict[str, Any] | None = None,
) -> Iterator[RequestContext]:
    """Set up a system-level RequestContext (tenant=None) for global operations."""
    ctx = RequestContext(
        actor=actor or None,
        tenant=None,
        memberships=(),
        permissions=frozenset(),
        workspace=None,
        extra=extra or {},
    )
    token = set_request_context(ctx)
    try:
        yield ctx
    finally:
        reset_request_context(token)


def build_context(
    tenant: Tenant | None = None,
    workspace: Workspace | None = None,
    actor: User | None = None,
    org_node: OrganizationNode | None = None,
    **extra: Any,
) -> dict[str, Any]:
    """Build a context dict for passing to fixture providers.

    This does NOT set the RequestContext — it only builds the dict.
    Use `tenant_scope`/`workspace_scope` to activate the context.
    """
    context: dict[str, Any] = {"extra": extra}
    if tenant is not None:
        context["tenant"] = tenant
        context["tenant_id"] = tenant.pk
        context["tenant_slug"] = tenant.slug
    if workspace is not None:
        context["workspace"] = workspace
        context["workspace_id"] = workspace.pk
        context["workspace_slug"] = workspace.slug
    if actor is not None:
        context["actor"] = actor
        context["actor_id"] = actor.pk
    if org_node is not None:
        context["org_node"] = org_node
        context["org_node_id"] = org_node.pk
    return context
