"""Agent 与双端前端之间的稳定结构化协议。"""

from dataclasses import dataclass, field
from typing import Annotated, Any, Literal

from pydantic import BaseModel, ConfigDict, Field


class StrictModel(BaseModel):
    """拒绝未声明字段，防止模型生成内容悄悄穿透协议。"""

    model_config = ConfigDict(extra="forbid")


class ProductPlanView(StrictModel):
    id: str
    code: str
    name: str
    summary: str = ""
    premium_cents: int = Field(ge=0)
    coverage_amount_cents: int = Field(ge=0)
    min_age: int = Field(ge=0, le=120)
    max_age: int = Field(ge=0, le=120)


class ProductView(StrictModel):
    product_id: str
    product_code: str
    name: str
    category: str
    summary: str
    product_version_id: str
    plans: list[ProductPlanView]


class ProductRecommendationsBlock(StrictModel):
    type: Literal["product_recommendations"] = "product_recommendations"
    version: Literal["1.0"] = "1.0"
    title: str = "为你匹配的保障方案"
    items: list[ProductView] = Field(max_length=2)


class QuoteView(StrictModel):
    eligible: bool
    quote_id: str | None = None
    product_id: str | None = None
    product_name: str
    plan_id: str | None = None
    plan_name: str
    premium_cents: int | None = None
    currency: str = "CNY"
    expires_at: str | None = None
    reason: str | None = None


class QuoteBlock(StrictModel):
    type: Literal["quote"] = "quote"
    version: Literal["1.0"] = "1.0"
    title: str = "保费测算结果"
    quote: QuoteView


class BusinessListBlock(StrictModel):
    type: Literal["business_list"] = "business_list"
    version: Literal["1.0"] = "1.0"
    title: str
    entity: Literal["order", "policy", "product", "attribution"]
    items: list[dict[str, Any]]
    total: int = Field(ge=0)


class MetricItem(StrictModel):
    label: str
    value: int | float | str
    unit: str = ""
    trend: str | None = None


class MetricsBlock(StrictModel):
    type: Literal["metrics"] = "metrics"
    version: Literal["1.0"] = "1.0"
    title: str
    items: list[MetricItem]


class ChartPoint(StrictModel):
    label: str
    value: int | float


class ChartBlock(StrictModel):
    type: Literal["chart"] = "chart"
    version: Literal["1.0"] = "1.0"
    title: str
    chart_type: Literal["line", "bar"]
    unit: str = ""
    points: list[ChartPoint] = Field(max_length=100)


class KnowledgeSourceView(StrictModel):
    document_id: str
    document_version: int
    title: str
    source_name: str
    excerpt: str
    excerpts: list[str] = Field(default_factory=list, max_length=2)
    score: float


class KnowledgeSourcesBlock(StrictModel):
    type: Literal["knowledge_sources"] = "knowledge_sources"
    version: Literal["1.0"] = "1.0"
    title: str = "回答依据"
    items: list[KnowledgeSourceView] = Field(max_length=5)


UiBlock = Annotated[
    ProductRecommendationsBlock
    | QuoteBlock
    | BusinessListBlock
    | MetricsBlock
    | ChartBlock
    | KnowledgeSourcesBlock,
    Field(discriminator="type"),
]


class AgentAction(StrictModel):
    """前端可执行动作；类型和负载都由 Harness 控制。"""

    type: Literal[
        "open_enrollment",
        "open_orders",
        "open_policies",
        "open_admin_orders",
        "open_admin_policies",
        "open_attribution",
        "none",
    ]
    label: str
    payload: dict[str, Any] = Field(default_factory=dict)
    confirmation_required: bool = False


class AgentStructuredOutput(StrictModel):
    """模型最终输出；业务数据必须来自 Tool，不允许模型自行填充。"""

    message: str = Field(min_length=1, max_length=1200)
    selected_product_codes: list[str] = Field(default_factory=list, max_length=2)
    suggested_action: AgentAction | None = None
    cited_knowledge_chunk_ids: list[str] = Field(
        default_factory=list,
        max_length=5,
        description=(
            "仅填写最终回答中实际采用、且能直接支持事实性结论的知识切片ID。"
            "检索候选、背景材料和无法确定的项目不得引用；没有直接证据时返回空数组。"
        ),
    )


class HarnessToolResult(StrictModel):
    """单个 Tool 的统一结果包络。"""

    summary: str
    data: dict[str, Any] = Field(default_factory=dict)
    blocks: list[UiBlock] = Field(default_factory=list)
    actions: list[AgentAction] = Field(default_factory=list)


@dataclass(frozen=True)
class AgentReply:
    """Agent Kernel 对应用层返回的结果。

    ``cards`` 沿用第一版 H5 字段名，内容已经升级为受校验的 UI Block。
    """

    text: str
    cards: list[dict[str, Any]]
    trace_id: str | None
    trace_url: str | None = None
    actions: list[dict[str, Any]] = field(default_factory=list)
    invoked_tools: tuple[str, ...] = ()
