"""T1.3 编排器单测——write 节点 + SSE 归一 + 图编译（ARCH §5.2/§7.3）。

全部注入 mock 网关，无真 LLM、无真 Postgres（图用 MemorySaver）。asyncio_mode=auto。
"""

from __future__ import annotations

import uuid

from fakes_orchestrator import FakeStreamGateway, RaisingGateway
from ww_agents import load_craft_doctrine
from ww_core.orchestrator import (
    EVENT_DONE,
    EVENT_ERROR,
    EVENT_TOKEN,
    ChapterState,
    build_write_request,
    normalize_deltas,
    stream_chapter_draft,
    write_node,
)
from ww_shared import AppError, ErrorCode

PROJECT = uuid.UUID("00000000-0000-0000-0000-000000000001")
USER = uuid.UUID("00000000-0000-0000-0000-0000000000aa")

STABLE = "## 世界观硬规则\n灵气可凝丹"
VOLATILE = "## 本章节拍\n主角破境"


# ---- build_write_request：纯函数、缓存断点、tier 不变量 ----


def test_build_write_request_puts_stable_core_in_cached_system_block() -> None:
    req = build_write_request(
        stable_core=STABLE, volatile=VOLATILE, user_id=USER, project_id=PROJECT
    )

    assert req.tier == "writer"  # 不变量②：只传档位，不传 model
    assert req.stream is True
    # 灵感①：system = [craft 教条(cache), stable_core(cache)]，craft 在最前。
    assert len(req.system) == 2
    assert all(b.cache is True for b in req.system)  # 不变量#9：稳定内核进缓存断点前
    assert req.system[1].text == STABLE
    assert req.input == VOLATILE
    assert req.scope.user_id == USER
    assert req.scope.project_id == PROJECT


# ---- 灵感①：写章 craft 教条块 ----


def test_write_request_includes_craft_block() -> None:
    req = build_write_request(
        stable_core=STABLE, volatile=VOLATILE, user_id=USER, project_id=PROJECT
    )
    # craft 教条作为独立的 cache=True system 块注入。
    assert req.system[0].text == load_craft_doctrine()
    assert req.system[0].cache is True


def test_craft_precedes_stable_core() -> None:
    req = build_write_request(
        stable_core=STABLE, volatile=VOLATILE, user_id=USER, project_id=PROJECT
    )
    # craft 教条块必须在 stable_core 块之前（跨书前缀复用最大化）。
    assert req.system[0].text == load_craft_doctrine()
    assert req.system[1].text == STABLE


def test_craft_is_byte_stable() -> None:
    # 不变量#9：craft 块字节稳定——两次构造完全一致，无每章易变项掺入。
    req1 = build_write_request(
        stable_core=STABLE, volatile="请创作第 1 章的正文。", user_id=USER, project_id=PROJECT
    )
    req2 = build_write_request(
        stable_core=STABLE, volatile="请创作第 99 章的正文。", user_id=USER, project_id=PROJECT
    )
    assert req1.system[0].text == req2.system[0].text  # craft 块与章号无关


def test_no_per_chapter_field_in_cached_system_blocks() -> None:
    # 不变量#9：每章易变项（章号）只在 input(断点后)，绝不进任何 cache=True 的 system 块。
    volatile = "## 写作指令\n请创作第 5 章的正文。"
    req = build_write_request(
        stable_core=STABLE, volatile=volatile, user_id=USER, project_id=PROJECT
    )
    for block in req.system:
        assert "第 5 章" not in block.text
    assert "第 5 章" in req.input


# ---- stream_chapter_draft：转发网关 Delta、构造正确请求 ----


async def test_stream_chapter_draft_yields_deltas_and_builds_request() -> None:
    gateway = FakeStreamGateway(["灵", "气", "破", "境"])

    texts = [
        d.text
        async for d in stream_chapter_draft(
            gateway, stable_core=STABLE, volatile=VOLATILE, user_id=USER, project_id=PROJECT
        )
    ]

    assert texts == ["灵", "气", "破", "境"]
    assert gateway.call_count == 1
    assert gateway.last_request is not None
    assert gateway.last_request.tier == "writer"
    assert gateway.last_request.system[0].cache is True


# ---- write_node：累积草稿、不可变增量返回 ----


async def test_write_node_accumulates_draft() -> None:
    gateway = FakeStreamGateway(["第一段。", "第二段。"])
    state: ChapterState = {
        "project_id": PROJECT,
        "chapter_no": 7,
        "user_id": USER,
        "stable_core": STABLE,
        "volatile": VOLATILE,
        "draft": "",
    }

    result = await write_node(state, gateway=gateway)

    assert result == {"draft": "第一段。第二段。"}
    assert state["draft"] == ""  # 不可变：未原地改入参


# ---- SSE 归一：token / done ----


async def test_normalize_deltas_emits_tokens_then_done() -> None:
    gateway = FakeStreamGateway(["a", "bc", ""])  # 空 Delta 不应产 token
    deltas = stream_chapter_draft(
        gateway, stable_core=STABLE, volatile=VOLATILE, user_id=USER, project_id=PROJECT
    )

    events = [e async for e in normalize_deltas(deltas)]

    assert [e.event for e in events] == [EVENT_TOKEN, EVENT_TOKEN, EVENT_DONE]
    assert events[0].data == {"text": "a"}
    assert events[1].data == {"text": "bc"}
    assert events[-1].data == {"length": 3}  # "a"+"bc"


# ---- SSE 归一：中途失败 → error 事件（不向上抛） ----


async def test_normalize_deltas_emits_error_on_app_error() -> None:
    gateway = RaisingGateway(["半句"], AppError(ErrorCode.LLM_UNAVAILABLE, "fallbacks exhausted"))
    deltas = stream_chapter_draft(
        gateway, stable_core=STABLE, volatile=VOLATILE, user_id=USER, project_id=PROJECT
    )

    events = [e async for e in normalize_deltas(deltas, request_id="req-1")]

    assert [e.event for e in events] == [EVENT_TOKEN, EVENT_ERROR]  # 无 done
    err = events[-1].data
    assert err["code"] == ErrorCode.LLM_UNAVAILABLE
    assert err["request_id"] == "req-1"


async def test_normalize_deltas_maps_unexpected_error_to_internal() -> None:
    gateway = RaisingGateway([], RuntimeError("boom"))
    deltas = stream_chapter_draft(
        gateway, stable_core=STABLE, volatile=VOLATILE, user_id=USER, project_id=PROJECT
    )

    events = [e async for e in normalize_deltas(deltas)]

    assert [e.event for e in events] == [EVENT_ERROR]
    assert events[0].data["code"] == ErrorCode.INTERNAL


# 单 `write` 节点图 `build_write_graph` 已删（链图取代，见 chain-workflow 设计 §3.3）；
# write 节点本体 `write_node` 仍由上面的 stream/run 用例直接覆盖（注入 mock 网关，无需图运行时）。
