"""文风提取节点（C6 扩 / ARCH §5.4 style-auditor 提取轨 / §6.9）。

文风提取是**独立生成**——不在写章/审稿流水线（不接进 review 图），仿 `outline_node`。
端点（T4.3）经 BackgroundTask 调 `run_style_extraction` 拿结构化 `StyleFingerprintResult`，
再持久化到 `style_fingerprint` 表（version 化）。

确定性边界（CLAUDE.md「LangGraph」纪律）：
- 节点逻辑里**无 LLM 非确定性**——不确定性藏在网关后；节点只做
  `AgentSpec + samples → LlmRequest` 的纯构造 + 转发 `Gateway.run` 的 `parsed`。
- 因此注入 mock 网关（产 `parsed`）即可单测，无需图运行时、无需真 Postgres。

不变量 #2：agent 只声明 `tier`，绝不传具体 model（spec.tier 透传）。
不变量 #3：节点**只读、不写库**——结构化指纹返回调用方，落库经端点（T4.3）。
不变量 #9：`spec.system_prompt` 进缓存断点前块（cache=True）；样本进 `input`（断点后）。
"""

from __future__ import annotations

import uuid

import structlog
from ww_agents import AgentSpec, StyleFingerprintResult
from ww_llm_gateway.types import Block, LlmRequest, Scope

from ._protocols import GatewayRun

log = structlog.get_logger(__name__)


def build_style_extract_request(
    spec: AgentSpec,
    *,
    samples_text: str,
    user_id: uuid.UUID,
    project_id: uuid.UUID,
) -> LlmRequest:
    """据 spec + 样本正文构造文风提取请求（纯函数）。

    `spec.system_prompt` → `system` 缓存断点前块（cache=True，不变量 #9）；
    `samples_text`（拼接的样本正文）→ `input`（断点后）。
    提取用 `run()` 非 `stream()`（要结构化指纹，非流式正文）。
    """
    return LlmRequest(
        tier=spec.tier,  # 不变量 #2：只传档位，不传 model
        system=[Block(text=spec.system_prompt, cache=True)],
        input=samples_text,
        output_schema=spec.output_schema,
        scope=Scope(user_id=user_id, project_id=project_id),
    )


async def run_style_extraction(
    spec: AgentSpec,
    *,
    samples_text: str,
    gateway: GatewayRun,
    user_id: uuid.UUID,
    project_id: uuid.UUID,
) -> StyleFingerprintResult:
    """跑文风提取：构请求 → `gateway.run` → 返回结构化 `StyleFingerprintResult`。

    裸函数（显式 gateway 关键字），注入 mock 网关即可单测，无需图运行时。
    提取是独立生成（非并行审）：网关失败直接上抛（端点/T4.3 处理），不静默吞。
    **只产结构化指纹、不写库**（持久化属 T4.3，不变量 #3）。
    """
    req = build_style_extract_request(
        spec, samples_text=samples_text, user_id=user_id, project_id=project_id
    )
    resp = await gateway.run(req)
    parsed = resp.parsed
    if not isinstance(parsed, StyleFingerprintResult):
        # 带 output_schema 时 parsed 必非 None（C1）；违约则明确报错而非返回空指纹。
        log.error(
            "style_extract_node_missing_parsed",
            project_id=str(project_id),
            parsed_type=type(parsed).__name__,
        )
        raise ValueError("gateway returned no parsed StyleFingerprintResult for style request")
    return parsed
