"""第 2 批单测:世界状态持久性、寻路与逐跳、注意力打断、共识层、引擎端到端(无 LLM)。"""

import asyncio

from genesis.runtime.actor import Actor
from genesis.runtime.attention import Attention
from genesis.runtime.bulletin import dynamic_bulletin, static_common_ground
from genesis.runtime.bus import Event
from genesis.runtime.engine import WorldEngine
from genesis.runtime.simclock import SimClock
from genesis.runtime.worldstate import Item, WorldState, seed_ravenisle


class FakeTime:
    def __init__(self):
        self.t = 0.0

    def __call__(self):
        return self.t


def _ravenisle_state() -> WorldState:
    from genesis.manifest.builder import WorldBuilder
    b = WorldBuilder.from_directory("worlds/ravenisle")
    places = {pm.name: {"description": pm.description or "", "geography": pm.geography,
                        "adjacent": [b.places[a].name for a in pm.adjacent if a in b.places]}
              for pm in b.places.values()}
    ws = WorldState(places)
    seed_ravenisle(ws)
    return ws


# ───────────────── WorldState:客观状态是唯一真理 ─────────────────

def test_facility_state_persists_and_visible_to_later_visitors():
    ws = _ravenisle_state()
    ws.positions["江离"] = "发电机房"
    ws.set_facility("switchboard", "smashed")
    ws.power_on = False
    ws.positions["温言"] = "发电机房"      # 后来者
    seen = " ".join(ws.describe("发电机房", viewer="温言"))
    assert "砸毁" in seen                  # 看到的是持久的客观事实
    assert "江离" in seen                  # 在场者可见
    ws.positions["白聿"] = "酒窖"
    assert "漆黑" in " ".join(ws.describe("酒窖", viewer="白聿"))  # 停电波及全部室内


def test_item_take_and_destroy_feedback():
    ws = _ravenisle_state()
    ws.positions["温言"] = "书房"
    it = ws.items["draft"]
    assert it.hidden                        # 需要搜索的物品不直接显示
    got = ws.take_item("draft", "温言")
    assert got is not None and got.holder == "温言"
    assert ws.take_item("draft", "顾长风") is None   # 已被拿走,别人拿不到
    ws.destroy_item("ledger")
    assert ws.items["ledger"].destroyed


def test_pathfinding_through_castle():
    ws = _ravenisle_state()
    hops = ws.path("门厅", "江离的房间")
    assert hops == ["二层走廊", "江离的房间"]          # 必经走廊(目击通道)
    hops2 = ws.path("厨房", "船屋")
    assert hops2[0] in ("门厅", "酒窖")                # 多路可达
    assert ws.path("门厅", "门厅") == []


# ───────────────── Actor:逐跳移动 + 时长 ─────────────────

def test_actor_moves_hop_by_hop_with_duration():
    ws = _ravenisle_state()
    ft = FakeTime()
    clock = SimClock(rate=1, start_hour=21, time_fn=ft)
    ws.positions["温言"] = "门厅"
    actor = Actor("温言", ws, clock)
    act = actor.start_move("温言的房间")
    assert act is not None and act.path == ["二层走廊", "温言的房间"]
    assert act.until > clock.sim_seconds              # 第一跳要花时间
    status, where = actor.advance()
    assert (status, where) == ("hop", "二层走廊")
    assert ws.positions["温言"] == "二层走廊"          # 途经地点真实出现(可被目击)
    status, where = actor.advance()
    assert (status, where) == ("arrived", "温言的房间")
    assert actor.current is None


def test_actor_interruptible():
    ws = _ravenisle_state()
    clock = SimClock(rate=1, start_hour=21, time_fn=FakeTime())
    ws.positions["白聿"] = "门厅"
    actor = Actor("白聿", ws, clock)
    actor.start_sleep(7.0)
    assert actor.interrupt() == "睡觉"                 # 睡觉可被惊醒
    assert actor.current is None


# ───────────────── Attention:阈值打断 + 低显著度压缩 ─────────────────

def test_attention_interrupt_and_dedup():
    fired = []
    att = Attention("白聿", on_interrupt=lambda e: fired.append(e.content))
    att.receive(Event(etype="transit", content="温言走了进来", actor="温言", salience=0.3))
    att.receive(Event(etype="transit", content="温言离开了这里", actor="温言", salience=0.25))
    att.receive(Event(etype="ambient", content="惨叫!", salience=0.9))
    assert fired == ["惨叫!"]
    drained = att.drain()
    assert len(drained) == 2                           # 温言的两条压缩成最新一条
    assert drained[0].content == "温言离开了这里"
    assert att.drain() == []


# ───────────────── CommonGround ─────────────────

def test_bulletin_contents():
    static = static_common_ground(["甲", "乙"], {})
    assert "雾鸦岛地图常识" in static and "渡船" in static and "甲、乙" in static
    dyn = dynamic_bulletin("第1天 23:30 深夜", "雷暴", False, ["程亦深"])
    assert "停电" in dyn and "程亦深" in dyn and "雷暴" in dyn


# ───────────────── 引擎端到端(规则桩,无 LLM)─────────────────

async def _drive(eng, iters: int, ft, step_real: float = 0.5, sleep_s: float = 0.05):
    task = asyncio.create_task(eng.sched.run(eng.on_wake))
    for _ in range(iters):
        ft.t += step_real
        await asyncio.sleep(sleep_s)
    eng.sched.stop()
    await asyncio.wait_for(task, timeout=2)


def test_engine_agents_disperse_independently():
    """停电之前:7 人应从门厅各走各的(独立时间线,不齐步走)。"""
    async def main():
        ft = FakeTime()
        eng = WorldEngine("worlds/ravenisle", rate=600.0, time_fn=ft, demo_beats=False)
        eng.start()
        await _drive(eng, iters=50, ft=ft)   # ≈ 4 模拟小时
        return eng

    eng = asyncio.run(main())
    places = set(eng.state.positions.values())
    assert len(places) >= 3, f"应当分散活动,实际:{eng.state.positions}"
    frame = eng.frame(1)
    assert len(frame["agents"]) == 7 and frame["killer"] == "江离"


def test_engine_blackout_interrupts_and_gathers():
    """停电 demo:高显著度事件打断各自动作 → 恐惧驱动向门厅聚集;设施状态持久。"""
    async def main():
        ft = FakeTime()
        eng = WorldEngine("worlds/ravenisle", rate=600.0, time_fn=ft, demo_beats=True)
        eng.start()
        task = asyncio.create_task(eng.sched.run(eng.on_wake))
        for _ in range(31):                  # 推进到 23:30+ 停电触发
            ft.t += 0.5
            await asyncio.sleep(0.05)
        snap_mid = None
        for _ in range(10):                  # 停电后 ~50 模拟分钟内观察聚集
            ft.t += 0.5
            await asyncio.sleep(0.05)
            n = sum(1 for p in eng.state.positions.values() if p == "门厅")
            if snap_mid is None or n > snap_mid:
                snap_mid = n
        eng.sched.stop()
        await asyncio.wait_for(task, timeout=2)
        return eng, snap_mid

    eng, at_hall = asyncio.run(main())
    assert eng.state.facilities["switchboard"].state in ("smashed", "repaired")
    assert any("停电" in n["text"] for n in eng.narrative)
    assert at_hall >= 4, f"惊动后应多数人涌向门厅,峰值仅 {at_hall}:{eng.state.positions}"
