from __future__ import annotations

import asyncio
import contextvars
import typing

from ..utils import bounded_executor


T = typing.TypeVar('T')


def create_detached_task(
    coro: typing.Coroutine[typing.Any, typing.Any, T],
    *,
    loop: asyncio.AbstractEventLoop | None = None,
    name: str | None = None,
    after_commit_manager: typing.Any | None = None,
    workspace_uuid: str | None = None,
) -> asyncio.Task[T]:
    """Create a task that inherits no request-local ContextVars.

    A normal ``asyncio.create_task`` copies the caller's context.  That is
    unsafe for work which outlives an HTTP request because it can copy the
    request's active database transaction or trusted tenant scope into a
    different asyncio task. Detached work must receive durable identity such
    as ``ExecutionContext`` through explicit arguments and establish its own
    tenant scope or unit of work whenever it accesses persistence.
    """

    task_loop = loop or asyncio.get_running_loop()
    gate: asyncio.Future[None] | None = None
    # Inspect the type so dynamic Mock/AsyncMock attributes do not turn into a
    # fake gate in lightweight tests or embedders.
    gate_factory = getattr(type(after_commit_manager), 'create_after_commit_gate', None)
    if callable(gate_factory):
        gate = gate_factory(after_commit_manager)
    task_coro = _wait_for_commit(coro, gate) if gate is not None else coro
    if workspace_uuid is not None:
        task_coro = bounded_executor.run_in_blocking_work_scope(
            task_coro,
            workspace_uuid,
        )
    return task_loop.create_task(task_coro, name=name, context=contextvars.Context())


async def _wait_for_commit(
    coro: typing.Coroutine[typing.Any, typing.Any, T],
    gate: asyncio.Future[None],
) -> T:
    try:
        await gate
    except BaseException:
        coro.close()
        raise
    return await coro


async def run_in_workspace_uow(
    ap: typing.Any,
    workspace_uuid: str,
    operation: typing.Callable[[], typing.Awaitable[T]],
) -> T:
    """Run one short persistence section in a detached Cloud task scope.

    This helper deliberately scopes only the supplied operation.  Callers
    should not wrap long-running network or runtime work in a database
    transaction.
    """

    persistence_mgr = getattr(ap, 'persistence_mgr', None)
    if persistence_mgr is None:
        return await operation()
    cloud_runtime = getattr(getattr(persistence_mgr, 'mode', None), 'value', None) == 'cloud_runtime'
    if not cloud_runtime:
        return await operation()

    tenant_uow = getattr(persistence_mgr, 'tenant_uow', None)
    if not callable(tenant_uow):
        raise RuntimeError('Detached Cloud tasks require an explicit tenant unit of work')
    async with tenant_uow(workspace_uuid):
        return await operation()
