"""T5.4 能力协商 / 降级（ARCH §4.4）。

结构化输出：原生不支持时降级（instructor JSON-提示路径仍由适配器自处理；网关
层负责在链内**优先选支持结构化输出的 provider**，无则降级到首个可用、对上层透明）。
降级时 `served_by.degraded=True` 标注，记账/日志可见，正确性不受影响。
"""

from __future__ import annotations

import uuid

from fakes_resilience import FakeLedger, ScriptedAdapter, chain, chain_resolver
from pydantic import BaseModel
from ww_llm_gateway.adapters.base import Capabilities
from ww_llm_gateway.gateway import Gateway
from ww_llm_gateway.types import Block, LlmRequest, Scope


class Tiny(BaseModel):
    x: str


def _structured_req() -> LlmRequest:
    return LlmRequest(
        tier="analyst",
        input="给我结构化",
        output_schema=Tiny,
        scope=Scope(user_id=uuid.UUID(int=1)),
    )


async def test_prefers_structured_capable_provider_in_chain() -> None:
    # 主 provider 不支持结构化输出，回退 provider 支持 → 网关优先选支持者服务结构化请求。
    no_struct = ScriptedAdapter(
        "weakprov",
        capabilities_=Capabilities(structured_output=False, prefix_cache=False),
    )
    struct = ScriptedAdapter(
        "strongprov",
        capabilities_=Capabilities(structured_output=True, prefix_cache=True),
    )
    ledger = FakeLedger()
    gw = Gateway(
        {"weakprov": no_struct, "strongprov": struct},
        ledger,
        chain_resolver=chain_resolver(chain(("weakprov", "w"), ("strongprov", "s"))),
    )

    resp = await gw.run(_structured_req())

    assert resp.served_by.provider == "strongprov"
    assert resp.parsed is not None
    assert no_struct.complete_calls == 0


async def test_degrades_when_no_structured_capable_provider() -> None:
    # 链上无 provider 支持结构化输出 → 降级用首个可用（适配器自走 instructor JSON 提示）
    # 并标 served_by.degraded=True，不硬失败。
    weak = ScriptedAdapter(
        "weakprov",
        capabilities_=Capabilities(structured_output=False),
    )
    gw = Gateway(
        {"weakprov": weak},
        FakeLedger(),
        chain_resolver=chain_resolver(chain(("weakprov", "w"))),
    )

    resp = await gw.run(_structured_req())

    assert resp.served_by.provider == "weakprov"
    assert resp.served_by.degraded is True
    assert weak.complete_calls == 1


async def test_no_degradation_flag_for_plain_text() -> None:
    # 纯文本请求对任何 provider 都不算降级。
    weak = ScriptedAdapter("weakprov", capabilities_=Capabilities(structured_output=False))
    gw = Gateway(
        {"weakprov": weak},
        FakeLedger(),
        chain_resolver=chain_resolver(chain(("weakprov", "w"))),
    )
    req = LlmRequest(tier="writer", input="正文", scope=Scope(user_id=uuid.UUID(int=1)))

    resp = await gw.run(req)

    assert resp.served_by.degraded is False


async def test_cache_blocks_passed_through_regardless_of_capability() -> None:
    # 前缀缓存不支持时只是跳过，不改正确性、不算降级。
    weak = ScriptedAdapter("weakprov", capabilities_=Capabilities(prefix_cache=False))
    gw = Gateway(
        {"weakprov": weak},
        FakeLedger(),
        chain_resolver=chain_resolver(chain(("weakprov", "w"))),
    )
    req = LlmRequest(
        tier="writer",
        input="正文",
        system=[Block(text="世界观硬规则", cache=True)],
        scope=Scope(user_id=uuid.UUID(int=1)),
    )

    resp = await gw.run(req)

    assert resp.text
    assert resp.served_by.degraded is False
