"""T4.2 文风提取节点单测——build_style_extract_request + run_style_extraction（C6 扩 / ARCH §5.4）。

注入 mock 网关（产 `StyleFingerprintResult` parsed），无真 LLM、无真 Postgres。
提取节点是裸函数（独立生成、仿 outliner，不在写章/审稿流水线）；只产结构化指纹，**不写库**
（持久化属 T4.3 端点，不变量 #3）。asyncio_mode=auto。
"""

from __future__ import annotations

import uuid

import pytest
from fakes_orchestrator import FailingRunGateway, FakeRunGateway
from ww_agents import (
    StyleDimension,
    StyleFingerprintResult,
    style_extract_spec,
)
from ww_core.orchestrator import build_style_extract_request, run_style_extraction

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

SAMPLES = "样本一：他一刀劈下，血溅三尺。\n样本二：风停了，只剩呼吸声。"

_FINGERPRINT = StyleFingerprintResult(
    dimensions=[
        StyleDimension(
            name="句长节奏",
            value="短句为主、节奏明快",
            evidence=["他一刀劈下，血溅三尺。"],
        ),
        StyleDimension(name="叙事人称", value="第三人称限知", evidence=["风停了"]),
    ]
)


# ---- build_style_extract_request：tier 不变量、缓存断点、output_schema、run 非 stream ----


def test_build_style_extract_request_uses_spec_tier_and_cached_prompt() -> None:
    req = build_style_extract_request(
        style_extract_spec, samples_text=SAMPLES, user_id=USER, project_id=PROJECT
    )

    assert req.tier == "analyst"  # 不变量②：spec 档位透传，不传 model
    assert req.stream is False  # 提取用 run() 非 stream()
    assert len(req.system) == 1
    assert req.system[0].text == style_extract_spec.system_prompt
    assert req.system[0].cache is True  # 不变量#9
    assert req.input == SAMPLES
    assert req.output_schema is StyleFingerprintResult
    assert req.scope.user_id == USER
    assert req.scope.project_id == PROJECT


# ---- run_style_extraction：返回结构化指纹含证据，只读不写库 ----


async def test_run_style_extraction_returns_fingerprint() -> None:
    gateway = FakeRunGateway(_FINGERPRINT)

    result = await run_style_extraction(
        style_extract_spec,
        samples_text=SAMPLES,
        gateway=gateway,
        user_id=USER,
        project_id=PROJECT,
    )

    assert isinstance(result, StyleFingerprintResult)
    assert len(result.dimensions) == 2
    assert result.dimensions[0].name == "句长节奏"
    assert result.dimensions[0].evidence == ["他一刀劈下，血溅三尺。"]
    assert gateway.call_count == 1


async def test_run_style_extraction_forwards_constructed_request() -> None:
    gateway = FakeRunGateway(_FINGERPRINT)

    await run_style_extraction(
        style_extract_spec,
        samples_text=SAMPLES,
        gateway=gateway,
        user_id=USER,
        project_id=PROJECT,
    )

    req = gateway.last_request
    assert req is not None
    assert req.tier == "analyst"
    assert req.output_schema is StyleFingerprintResult
    assert req.input == SAMPLES


async def test_run_style_extraction_raises_when_gateway_fails() -> None:
    # 提取是独立生成（非流水线并行审）；网关失败直接上抛，端点/T4.3 处理。
    gateway = FailingRunGateway(RuntimeError("provider down"))

    with pytest.raises(RuntimeError):
        await run_style_extraction(
            style_extract_spec,
            samples_text=SAMPLES,
            gateway=gateway,
            user_id=USER,
            project_id=PROJECT,
        )


async def test_run_style_extraction_raises_when_parsed_missing() -> None:
    # 带 output_schema 时 parsed 必非 None（C1）；若网关违约返回 None，明确报错而非静默。
    class _NoParseGateway:
        async def run(self, req: object) -> object:
            from ww_llm_gateway.types import LlmResponse, ServedBy, Usage

            return LlmResponse(
                text="{}",
                parsed=None,
                usage=Usage(
                    provider="fake",
                    model="fake-analyst",
                    input_tokens=1,
                    output_tokens=1,
                    cost_minor=0,
                    currency="USD",
                ),
                served_by=ServedBy(provider="fake", model="fake-analyst"),
            )

    with pytest.raises(ValueError, match="parsed"):
        await run_style_extraction(
            style_extract_spec,
            samples_text=SAMPLES,
            gateway=_NoParseGateway(),  # type: ignore[arg-type]
            user_id=USER,
            project_id=PROJECT,
        )
