from __future__ import annotations

"""Actor execution context for system calls inside actor methods."""

import contextlib
import contextvars
from typing import Any, Awaitable

from flownet.api.actor_method_ref import ActorMethodRef
from flownet.runtime.kernel import RuntimeKernel, get_runtime_kernel


_system_calls_allowed: contextvars.ContextVar[bool] = contextvars.ContextVar(
    "flownet_system_calls_allowed",
    default=True,
)
_actor_context_var: contextvars.ContextVar["ActorContext | None"] = contextvars.ContextVar(
    "flownet_actor_context",
    default=None,
)


def system_calls_allowed() -> bool:
    return _system_calls_allowed.get()


@contextlib.contextmanager
def _system_calls_scope(allowed: bool):
    token = _system_calls_allowed.set(allowed)
    try:
        yield
    finally:
        _system_calls_allowed.reset(token)


@contextlib.contextmanager
def _user_execution_scope(actor_ctx: "ActorContext | None" = None):
    token_calls = _system_calls_allowed.set(False)
    token_actor = _actor_context_var.set(actor_ctx)
    try:
        yield
    finally:
        _actor_context_var.reset(token_actor)
        _system_calls_allowed.reset(token_calls)


class ActorContext:
    def __init__(self, kernel: RuntimeKernel):
        self._kernel = kernel

    def local_address(self) -> str:
        return self._kernel.local_address()

    def call_async(self, rmf: ActorMethodRef, *args, **kwargs):
        if not isinstance(rmf, ActorMethodRef):
            raise TypeError("ActorContext.call_async expects an ActorMethodRef.")
        with _system_calls_scope(True):
            actors = self._kernel.require_actors()
            tasks = self._kernel.require_tasks()
            coro = actors.call(rmf, *args, **kwargs)
            return tasks.submit_runtime_coro(coro)

    def call(self, rmf: ActorMethodRef, *args, **kwargs):
        future = self.call_async(rmf, *args, **kwargs)
        return future.result()

    def submit(self, coro: Awaitable[Any], *, loop: str = "user"):
        with _system_calls_scope(True):
            return self._kernel.require_tasks().submit(coro, loop=loop)

    def emit_service_event(
        self,
        service_name: str,
        event_type: str,
        payload: Any = None,
        tags: dict[str, str] | None = None,
    ) -> None:
        with _system_calls_scope(True):
            self._kernel.require_topics().emit_service_event(service_name, event_type, payload, tags)

    def emit_topic_event(
        self,
        topic_id: str,
        event_type: str,
        payload: Any = None,
        tags: dict[str, str] | None = None,
    ) -> None:
        with _system_calls_scope(True):
            self._kernel.require_topics().emit_topic_event(topic_id, event_type, payload, tags)


def actor_context() -> ActorContext:
    ctx = _actor_context_var.get()
    if ctx is None:
        raise RuntimeError("Actor context not available.")
    return ctx


def _build_actor_context(kernel: RuntimeKernel | None = None) -> ActorContext:
    if kernel is None:
        kernel = get_runtime_kernel()
    return ActorContext(kernel)


__all__ = [
    "ActorContext",
    "actor_context",
    "system_calls_allowed",
]
