"""Narrator(说书人)与天气系统单测(FakeLLM,零真实调用)。"""

import random

from genesis.runtime.bus import Event
from genesis.runtime.narrator import Narrator
from genesis.runtime.weather import ANNOUNCE, TRANSITIONS, next_weather


class FakeLLM:
    def __init__(self, reply='{"prose":"风声里,门厅的炉火忽明忽暗。","summary":"七人各怀鬼胎。"}'):
        self.reply = reply
        self.calls = []

    def complete(self, messages, **kw):
        self.calls.append(messages)
        return self.reply


def test_narrator_taps_public_only_and_writes():
    nar = Narrator(FakeLLM())
    nar.tap(Event(etype="speech", content="顾长风说:「都别乱走」", place="门厅", salience=0.45))
    nar.tap(Event(etype="feedback", content="你拿到了日记", scope="private", to=["唐曼"], salience=0.5))  # 私有不入书
    nar.tap(Event(etype="transit", content="温言走了进来", place="书房", salience=0.3))
    assert len(nar.buffer) == 2
    assert all("日记" not in b for b in nar.buffer)            # 防剧透:私有事件不进说书人视野
    assert nar.due(700.0)                                       # 过窗口即可成段
    nar.write(700.0, "第1天 22:10 夜晚")
    assert len(nar.paras) == 1 and "炉火" in nar.paras[0]["text"]
    assert nar.summary == "七人各怀鬼胎。"
    assert nar.buffer == [] and not nar.due(750.0)              # 缓冲清空,未到下个窗口不再写


def test_narrator_llm_failure_keeps_events():
    class BoomLLM:
        def complete(self, *a, **k):
            raise TimeoutError("boom")

    nar = Narrator(BoomLLM())
    nar.tap(Event(etype="ambient", content="炸雷", salience=0.6, scope="global"))
    nar.write(700.0, "第1天 22:10")
    assert nar.paras == [] and len(nar.buffer) == 1             # 事件退回,不丢


def test_narrator_plain_text_fallback():
    nar = Narrator(FakeLLM(reply="(没按格式)夜色像一块浸了水的黑布。"))
    nar.tap(Event(etype="speech", content="x", place="门厅", salience=0.5))
    nar.write(700.0, "第1天 22:10")
    assert "黑布" in nar.paras[0]["text"]                        # 非 JSON 输出整段兜底为正文


def test_weather_transitions_closed_and_announced():
    rng = random.Random(42)
    states = {"暴雨"}
    cur = "暴雨"
    for _ in range(200):
        cur = next_weather(cur, rng, hour=23.0)
        states.add(cur)
        assert cur in TRANSITIONS
    assert states >= {"暴雨", "狂风暴雨", "雷暴"}                  # 夜里风雨雷都来过
    assert all(s in ANNOUNCE for s in states)
    dawn = [next_weather("雷暴", random.Random(i), hour=5.5) for i in range(30)]
    assert dawn.count("雨歇") >= 12                               # 凌晨倾向雨歇(给天亮留呼吸)
