"""只读运行时教学模块：并发、调用次数、显式注册；无生产网关或写入保证。"""
import asyncio
import time
from tool_contracts import DEMO_USER, ToolFailure, dispatch


class Runtime:
    def __init__(self, allowed: set[str], max_calls: int = 4) -> None:
        self.allowed = set(allowed)
        self.max_calls = max_calls
        self.calls = 0
        self.slots = asyncio.Semaphore(2)

    async def call(self, name: str, arguments: dict, call_id: str, timeout: float = 5) -> dict:
        if name not in self.allowed:
            raise ToolFailure("tool_not_allowed")
        if self.calls >= self.max_calls:
            raise ToolFailure("tool_limit")
        # 本实例限单事件循环单任务使用；不是跨进程原子配额。
        self.calls += 1
        started = time.monotonic()

        async def execute() -> dict:
            async with self.slots:
                # 用线程接同步只读函数；取消 await 不保证停止底层线程。
                return await asyncio.to_thread(dispatch, name, arguments, DEMO_USER)

        try:
            data = await asyncio.wait_for(execute(), timeout=timeout)
            return {"call_id": call_id, "status": "success", "data": data,
                    "elapsed_seconds": time.monotonic() - started}
        except ToolFailure as error:
            return {"call_id": call_id, "status": "rejected", "error": str(error)}
        except asyncio.TimeoutError:
            return {"call_id": call_id, "status": "timeout", "error": "result_not_received"}


if __name__ == "__main__":
    async def main() -> None:
        runtime = Runtime({"get_order", "get_refund_policy"})
        # 两个教学查询互不依赖；每个结果显式带自己的 call_id。
        results = await asyncio.gather(
            runtime.call("get_order", {"order_id": "A1042"}, "c1"),
            runtime.call("get_refund_policy", {"order_id": "A1042"}, "c2"))
        print(results)
    asyncio.run(main())
