"""智保通第一阶段 Agent 可调用的确定性业务工具。

工具是模型与真实业务之间的唯一通道。模型负责决定“何时需要什么信息”，
工具负责参数校验、身份检查、调用领域服务并返回可信的结构化结果。
"""

from typing import Any, Literal, Protocol

from pydantic import BaseModel, ConfigDict, Field

from zbt.core.errors import AppError
from zbt.domains.catalog.service import ProductCatalogService
from zbt.domains.enrollment.service import EnrollmentService
from zbt.harness.policy import ToolPolicy
from zbt.harness.schemas import (
    AgentAction,
    BusinessListBlock,
    HarnessToolResult,
    MetricItem,
    MetricsBlock,
    QuoteBlock,
    QuoteView,
)
from zbt.harness.tooling import ToolDefinition, ToolExecutionContext, ToolRegistry


class AttributionMetricsProvider(Protocol):
    """推广指标查询协议，便于测试时替换实现。"""

    def performance(self, admin_user_id: str | None = None) -> dict[str, Any]: ...

    def order_ids(self, admin_user_id: str | None = None) -> set[str]: ...


class EmptyAttributionMetrics:
    """未装配推广模块时使用的空实现，保证工具仍可稳定返回。"""

    def performance(self, admin_user_id: str | None = None) -> dict[str, Any]:
        del admin_user_id
        return {
            "salesperson_count": 0,
            "visit_count": 0,
            "lead_count": 0,
            "order_count": 0,
            "premium_cents": 0,
            "items": [],
        }

    def order_ids(self, admin_user_id: str | None = None) -> set[str]:
        del admin_user_id
        return set()


class ToolArguments(BaseModel):
    """所有工具参数的基类；拒绝模型偷偷增加未声明字段。"""

    model_config = ConfigDict(extra="forbid")


class ListProductsArgs(ToolArguments):
    category: Literal["MEDICAL", "ACCIDENT"] | None = None


class CalculateQuoteArgs(ToolArguments):
    product_code: str = Field(min_length=2, max_length=32)
    plan_code: str = Field(min_length=2, max_length=32)
    age: int = Field(ge=0, le=120)
    region_code: str = Field(default="510100", pattern=r"^\d{6}$")
    occupation_code: str = Field(default="GENERAL", min_length=2, max_length=32)
    relationship: Literal["SELF", "PARENT", "SPOUSE", "CHILD"]


class PrepareEnrollmentArgs(ToolArguments):
    product_code: str = Field(min_length=2, max_length=32)
    plan_code: str = Field(min_length=2, max_length=32)
    age: int = Field(ge=0, le=120)
    relationship: Literal["SELF", "PARENT", "SPOUSE", "CHILD"]


class EmptyArgs(ToolArguments):
    pass


class RecentItemsArgs(ToolArguments):
    limit: int = Field(default=5, ge=1, le=20)


def build_agent_tool_registry(
    catalog: ProductCatalogService,
    enrollment: EnrollmentService,
    attribution: AttributionMetricsProvider | None = None,
) -> ToolRegistry:
    """创建并注册第一阶段全部 Agent 工具。

    嵌套函数是真正的工具处理器；函数末尾的 ``definitions`` 声明哪些人格可见。
    """

    registry = ToolRegistry()
    metrics = attribution or EmptyAttributionMetrics()

    def list_products(
        context: ToolExecutionContext,
        arguments: BaseModel,
    ) -> HarnessToolResult:
        """查询真实在售产品，推荐产品前必须先调用。"""

        del context
        args = _arguments(arguments, ListProductsArgs)
        products = catalog.list_available(category=args.category)
        return HarnessToolResult(
            summary=f"当前共有{len(products)}款符合条件的在售产品",
            data={"items": products, "total": len(products)},
        )

    def calculate_quote(
        context: ToolExecutionContext,
        arguments: BaseModel,
    ) -> HarnessToolResult:
        """按业务规则进行资格校验和保费测算，而不是让模型计算金额。"""

        args = _arguments(arguments, CalculateQuoteArgs)
        user = context.h5_user
        if user is None:
            raise AppError("AGENT_CUSTOMER_REQUIRED", "保费测算需要H5用户身份", 401)
        product, plan = _resolve_product_plan(
            catalog,
            args.product_code,
            args.plan_code,
        )
        # 资格不通过是可展示的业务结果；其他错误继续向上抛出。
        try:
            quote = enrollment.create_quote(
                user,
                product_id=str(product["product_id"]),
                plan_id=str(plan["id"]),
                age=args.age,
                region_code=args.region_code,
                occupation_code=args.occupation_code,
                relationship=args.relationship,
            )
        except AppError as error:
            if error.code != "ELIGIBILITY_REJECTED":
                raise
            block = QuoteBlock(
                quote=QuoteView(
                    eligible=False,
                    product_name=str(product["name"]),
                    plan_name=str(plan["name"]),
                    reason=error.message,
                )
            )
            return HarnessToolResult(
                summary=f"资格校验未通过：{error.message}",
                data={
                    "eligible": False,
                    "reason_code": (error.details or {}).get("reason_code"),
                },
                blocks=[block],
            )
        block = QuoteBlock(
            quote=QuoteView(
                eligible=True,
                quote_id=str(quote["quote_id"]),
                product_id=str(quote["product_id"]),
                product_name=str(product["name"]),
                plan_id=str(quote["plan_id"]),
                plan_name=str(plan["name"]),
                premium_cents=int(quote["premium_cents"]),
                currency=str(quote["currency"]),
                expires_at=str(quote["expires_at"]),
            )
        )
        return HarnessToolResult(
            summary=(
                f"{product['name']}{plan['name']}测算成功，"
                f"年保费{int(quote['premium_cents']) / 100:.2f}元"
            ),
            data=quote,
            blocks=[block],
            actions=[
                AgentAction(
                    type="open_enrollment",
                    label="填写投保信息",
                    payload={
                        "product_code": args.product_code,
                        "plan_code": args.plan_code,
                        "quote_id": quote["quote_id"],
                        "age": args.age,
                        "relationship": args.relationship,
                    },
                )
            ],
        )

    def prepare_enrollment(
        context: ToolExecutionContext,
        arguments: BaseModel,
    ) -> HarnessToolResult:
        """生成受控的投保页面入口，不在对话中收集证件等敏感信息。"""

        if context.h5_user is None:
            raise AppError("AGENT_CUSTOMER_REQUIRED", "投保引导需要H5用户身份", 401)
        args = _arguments(arguments, PrepareEnrollmentArgs)
        product, plan = _resolve_product_plan(
            catalog,
            args.product_code,
            args.plan_code,
        )
        action = AgentAction(
            type="open_enrollment",
            label="立即投保",
            payload={
                "product_id": product["product_id"],
                "product_code": args.product_code,
                "plan_id": plan["id"],
                "plan_code": args.plan_code,
                "age": args.age,
                "relationship": args.relationship,
            },
        )
        return HarnessToolResult(
            summary="已准备安全投保表单入口",
            data={"product": product, "plan": plan},
            actions=[action],
        )

    def list_my_orders(
        context: ToolExecutionContext,
        arguments: BaseModel,
    ) -> HarnessToolResult:
        """只查询当前 H5 用户自己的订单，并裁剪为安全展示字段。"""

        del arguments
        user = context.h5_user
        if user is None:
            raise AppError("AGENT_CUSTOMER_REQUIRED", "订单查询需要H5用户身份", 401)
        result = enrollment.list_orders(user.id)
        items = [_safe_order_item(item) for item in result["items"]]
        return HarnessToolResult(
            summary=f"查到{len(items)}笔本人投保订单",
            data={"items": items, "total": len(items)},
            blocks=[
                BusinessListBlock(
                    title="我的投保订单",
                    entity="order",
                    items=items[:5],
                    total=len(items),
                )
            ],
            actions=[AgentAction(type="open_orders", label="查看我的订单")],
        )

    def list_my_policies(
        context: ToolExecutionContext,
        arguments: BaseModel,
    ) -> HarnessToolResult:
        """只查询当前 H5 用户自己的电子保单。"""

        del arguments
        user = context.h5_user
        if user is None:
            raise AppError("AGENT_CUSTOMER_REQUIRED", "保单查询需要H5用户身份", 401)
        result = enrollment.list_policies(user)
        items = [_safe_policy_item(item) for item in result["items"]]
        return HarnessToolResult(
            summary=f"查到{len(items)}张本人保单",
            data={"items": items, "total": len(items)},
            blocks=[
                BusinessListBlock(
                    title="我的保障",
                    entity="policy",
                    items=items[:5],
                    total=len(items),
                )
            ],
            actions=[AgentAction(type="open_policies", label="查看我的保障")],
        )

    def overview(
        context: ToolExecutionContext,
        arguments: BaseModel,
    ) -> HarnessToolResult:
        """计算运营总览；SELF 数据范围只聚合当前运营人员归属的数据。"""

        del arguments
        orders = enrollment.list_orders()
        policies = enrollment.list_all_policies()
        if context.admin_user and context.admin_user.data_scope == "SELF":
            allowed = metrics.order_ids(context.admin_user.id)
            orders = _filter_business_result(orders, allowed, "order_id")
            policies = _filter_business_result(policies, allowed, "order_id")
        active = [item for item in policies["items"] if item["status"] == "ACTIVE"]
        premium_cents = sum(int(item["premium_cents"]) for item in active)
        items = [
            MetricItem(label="订单总量", value=int(orders["total"]), unit="笔"),
            MetricItem(label="有效保单", value=len(active), unit="张"),
            MetricItem(label="累计保费", value=premium_cents / 100, unit="元"),
        ]
        return HarnessToolResult(
            summary=(
                f"当前订单{orders['total']}笔、有效保单{len(active)}张、"
                f"累计保费{premium_cents / 100:.2f}元"
            ),
            data={
                "order_count": orders["total"],
                "active_policy_count": len(active),
                "premium_cents": premium_cents,
            },
            blocks=[MetricsBlock(title="经营总览", items=items)],
        )

    def recent_orders(
        context: ToolExecutionContext,
        arguments: BaseModel,
    ) -> HarnessToolResult:
        """按后台账号的数据范围返回近期订单。"""

        args = _arguments(arguments, RecentItemsArgs)
        result = enrollment.list_orders()
        if context.admin_user and context.admin_user.data_scope == "SELF":
            result = _filter_business_result(
                result,
                metrics.order_ids(context.admin_user.id),
                "order_id",
            )
        items = [_safe_order_item(item) for item in result["items"][: args.limit]]
        return HarnessToolResult(
            summary=f"返回最近{len(items)}笔订单",
            data={"items": items, "total": result["total"]},
            blocks=[
                BusinessListBlock(
                    title="近期订单",
                    entity="order",
                    items=items,
                    total=int(result["total"]),
                )
            ],
            actions=[AgentAction(type="open_admin_orders", label="进入订单中心")],
        )

    def recent_policies(
        context: ToolExecutionContext,
        arguments: BaseModel,
    ) -> HarnessToolResult:
        """按后台账号的数据范围返回近期保单。"""

        args = _arguments(arguments, RecentItemsArgs)
        result = enrollment.list_all_policies()
        if context.admin_user and context.admin_user.data_scope == "SELF":
            result = _filter_business_result(
                result,
                metrics.order_ids(context.admin_user.id),
                "order_id",
            )
        items = [_safe_policy_item(item) for item in result["items"][: args.limit]]
        return HarnessToolResult(
            summary=f"返回最近{len(items)}张保单",
            data={"items": items, "total": result["total"]},
            blocks=[
                BusinessListBlock(
                    title="近期保单",
                    entity="policy",
                    items=items,
                    total=int(result["total"]),
                )
            ],
            actions=[AgentAction(type="open_admin_policies", label="进入保单中心")],
        )

    def attribution_performance(
        context: ToolExecutionContext,
        arguments: BaseModel,
    ) -> HarnessToolResult:
        """查询推广访问、线索、归因订单和保费指标。"""

        del arguments
        admin = context.admin_user
        if admin is None:
            raise AppError("AGENT_ADMIN_REQUIRED", "推广分析需要后台身份", 401)
        owner_id = admin.id if admin.data_scope == "SELF" else None
        result = metrics.performance(owner_id)
        block = MetricsBlock(
            title="推广业绩",
            items=[
                MetricItem(
                    label="推广员",
                    value=int(result["salesperson_count"]),
                    unit="人",
                ),
                MetricItem(label="访问", value=int(result["visit_count"]), unit="次"),
                MetricItem(label="线索", value=int(result["lead_count"]), unit="人"),
                MetricItem(label="归因订单", value=int(result["order_count"]), unit="笔"),
                MetricItem(
                    label="归因保费",
                    value=int(result["premium_cents"]) / 100,
                    unit="元",
                ),
            ],
        )
        return HarnessToolResult(
            summary=(
                f"推广访问{result['visit_count']}次、线索{result['lead_count']}人、"
                f"归因订单{result['order_count']}笔"
            ),
            data=result,
            blocks=[block],
            actions=[AgentAction(type="open_attribution", label="查看推广明细")],
        )

    # 工具定义同时声明参数 Schema、自然语言说明、人格范围和副作用级别。
    # Agent Kernel 只会把当前人格有权使用的定义交给模型。
    definitions = (
        ToolDefinition(
            name="list_available_products",
            description="查询当前真实在售保险产品和计划；推荐产品前必须调用。",
            arguments=ListProductsArgs,
            policy=ToolPolicy(personas=("customer", "operation"), effect="read"),
            handler=list_products,
        ),
        ToolDefinition(
            name="calculate_insurance_quote",
            description="按真实产品、年龄、地区、职业和关系执行确定性资格校验与保费测算。",
            arguments=CalculateQuoteArgs,
            policy=ToolPolicy(personas=("customer",), effect="draft"),
            handler=calculate_quote,
        ),
        ToolDefinition(
            name="prepare_enrollment",
            description="为已明确的产品和计划生成安全投保表单入口，不采集身份证号。",
            arguments=PrepareEnrollmentArgs,
            policy=ToolPolicy(personas=("customer",), effect="draft"),
            handler=prepare_enrollment,
        ),
        ToolDefinition(
            name="list_my_orders",
            description="查询当前登录客户自己的投保订单。",
            arguments=EmptyArgs,
            policy=ToolPolicy(personas=("customer",), effect="read"),
            handler=list_my_orders,
        ),
        ToolDefinition(
            name="list_my_policies",
            description="查询当前登录客户自己的电子保单。",
            arguments=EmptyArgs,
            policy=ToolPolicy(personas=("customer",), effect="read"),
            handler=list_my_policies,
        ),
        ToolDefinition(
            name="get_operation_overview",
            description="查询订单、有效保单和累计保费经营指标。",
            arguments=EmptyArgs,
            policy=ToolPolicy(
                personas=("operation",),
                effect="read",
                required_permissions=("dashboard:read",),
            ),
            handler=overview,
        ),
        ToolDefinition(
            name="list_recent_orders",
            description="按当前管理员数据范围查询近期订单，只返回脱敏字段。",
            arguments=RecentItemsArgs,
            policy=ToolPolicy(
                personas=("operation",),
                effect="read",
                required_permissions=("order:read",),
            ),
            handler=recent_orders,
        ),
        ToolDefinition(
            name="list_recent_policies",
            description="按当前管理员数据范围查询近期保单，只返回脱敏字段。",
            arguments=RecentItemsArgs,
            policy=ToolPolicy(
                personas=("operation",),
                effect="read",
                required_permissions=("policy:read",),
            ),
            handler=recent_policies,
        ),
        ToolDefinition(
            name="get_attribution_performance",
            description="查询当前管理员数据范围内的推广访问、线索、订单和保费。",
            arguments=EmptyArgs,
            policy=ToolPolicy(
                personas=("operation",),
                effect="read",
                required_permissions=("attribution:read",),
            ),
            handler=attribution_performance,
        ),
    )
    for definition in definitions:
        registry.register(definition)
    return registry


def _arguments(value: BaseModel, expected: type[ToolArguments]) -> Any:
    """确保包装层传入了预期的参数模型。"""

    if not isinstance(value, expected):
        raise AppError("AGENT_TOOL_ARGUMENTS_INVALID", "工具参数类型错误", 500)
    return value


def _resolve_product_plan(
    catalog: ProductCatalogService,
    product_code: str,
    plan_code: str,
) -> tuple[dict[str, Any], dict[str, Any]]:
    """从真实在售目录中解析产品和计划，不接受模型虚构的编码。"""

    product = next(
        (item for item in catalog.list_available() if item["product_code"] == product_code),
        None,
    )
    if product is None:
        raise AppError("PRODUCT_NOT_AVAILABLE", "产品当前不可投保", 404)
    plan = next(
        (item for item in product["plans"] if item["code"] == plan_code),
        None,
    )
    if plan is None:
        raise AppError("PLAN_NOT_AVAILABLE", "保障计划当前不可投保", 404)
    return product, plan


def _safe_order_item(item: dict[str, Any]) -> dict[str, Any]:
    """只保留允许交给模型和前端的订单字段。"""

    return {
        "order_id": item["order_id"],
        "order_no": item["order_no"],
        "product_name": item["product_name"],
        "plan_name": item["plan_name"],
        "relationship": item["relationship"],
        "status": item["status"],
        "amount_cents": item["amount_cents"],
        "currency": item["currency"],
        "policy_no": item["policy_no"],
        "created_at": str(item["created_at"]),
    }


def _safe_policy_item(item: dict[str, Any]) -> dict[str, Any]:
    """只保留允许交给模型和前端的保单字段。"""

    return {
        "policy_id": item["policy_id"],
        "policy_no": item["policy_no"],
        "order_no": item["order_no"],
        "product_name": item["product_name"],
        "plan_name": item["plan_name"],
        "relationship": item["relationship"],
        "status": item["status"],
        "premium_cents": item["premium_cents"],
        "currency": item["currency"],
        "coverage_start": str(item["coverage_start"]),
        "coverage_end": str(item["coverage_end"]),
        "issued_at": str(item["issued_at"]),
    }


def _filter_business_result(
    result: dict[str, Any],
    allowed_ids: set[str],
    id_field: str,
) -> dict[str, Any]:
    """按照允许的业务 ID 过滤列表，并重新计算总数。"""

    items = [item for item in result["items"] if str(item.get(id_field, "")) in allowed_ids]
    return {"items": items, "total": len(items)}
