from __future__ import annotations

import json
from typing import Any

from mcp.server.fastmcp import FastMCP

from ..config import DEFAULT_PROFILE
from ..errors import QingflowApiError, backend_code_int, backend_code_value_int, is_auth_like_error, message_looks_like_invalid_token, raise_tool_error
from ..json_types import JSONObject
from ..list_type_labels import get_record_list_type_label
from .base import ToolBase


class ApprovalTools(ToolBase):
    """审批工具（中文名：审批数据与候选范围）。

    类型：审批辅助工具。
    主要职责：
    1. 查询审批相关列表与详情；
    2. 提供审批节点候选范围与运行时上下文辅助；
    3. 为任务执行类工具提供审批侧数据支撑。
    """

    def __init__(self, sessions, backend) -> None:  # type: ignore[no-untyped-def]
        """执行内部辅助逻辑。"""
        super().__init__(sessions, backend)
        self._form_id_cache: dict[str, int] = {}

    def register(self, mcp: FastMCP) -> None:
        """注册当前工具到 MCP 服务。"""
        @mcp.tool()
        def record_comment_write(
            profile: str = DEFAULT_PROFILE,
            app_key: str = "",
            record_id: int = 0,
            payload: dict[str, Any] | None = None,
        ) -> dict[str, Any]:
            return self.record_comment_write(profile=profile, app_key=app_key, record_id=record_id, payload=payload or {})

        @mcp.tool()
        def record_comment_list(
            profile: str = DEFAULT_PROFILE,
            app_key: str = "",
            record_id: int = 0,
            page_size: int = 20,
            list_type: int | None = None,
            page_num: int | None = 1,
        ) -> dict[str, Any]:
            return self.record_comment_list(
                profile=profile,
                app_key=app_key,
                apply_id=record_id,
                page_size=page_size,
                list_type=list_type,
                page_num=page_num,
            )

        @mcp.tool()
        def record_comment_mentions(
            profile: str = DEFAULT_PROFILE,
            app_key: str = "",
            record_id: int = 0,
            page_size: int = 20,
            page_num: int = 1,
            list_type: int | None = None,
            keyword: str | None = None,
        ) -> dict[str, Any]:
            return self.record_comment_mentions(
                profile=profile,
                app_key=app_key,
                record_id=record_id,
                page_size=page_size,
                page_num=page_num,
                list_type=list_type,
                keyword=keyword,
            )

        @mcp.tool()
        def record_comment_mark_read(profile: str = DEFAULT_PROFILE, app_key: str = "", record_id: int = 0) -> dict[str, Any]:
            return self.record_comment_mark_read(profile=profile, app_key=app_key, apply_id=record_id)

        @mcp.tool(description=self._high_risk_tool_description(operation="approve", target="workflow task"))
        def task_approve(
            profile: str = DEFAULT_PROFILE,
            app_key: str = "",
            record_id: int = 0,
            payload: dict[str, Any] | None = None,
            fields: dict[str, Any] | None = None,
        ) -> dict[str, Any]:
            return self.task_approve(profile=profile, app_key=app_key, record_id=record_id, payload=payload or {}, fields=fields or {})

        @mcp.tool(description=self._high_risk_tool_description(operation="reject", target="workflow task"))
        def task_reject(
            profile: str = DEFAULT_PROFILE,
            app_key: str = "",
            record_id: int = 0,
            payload: dict[str, Any] | None = None,
            fields: dict[str, Any] | None = None,
        ) -> dict[str, Any]:
            return self.task_reject(profile=profile, app_key=app_key, record_id=record_id, payload=payload or {}, fields=fields or {})

        @mcp.tool()
        def task_rollback_candidates(
            profile: str = DEFAULT_PROFILE,
            app_key: str = "",
            record_id: int = 0,
            workflow_node_id: int = 0,
        ) -> dict[str, Any]:
            return self.task_rollback_candidates(profile=profile, app_key=app_key, record_id=record_id, workflow_node_id=workflow_node_id)

        @mcp.tool()
        def task_rollback(
            profile: str = DEFAULT_PROFILE,
            app_key: str = "",
            record_id: int = 0,
            payload: dict[str, Any] | None = None,
            fields: dict[str, Any] | None = None,
        ) -> dict[str, Any]:
            return self.task_rollback(profile=profile, app_key=app_key, record_id=record_id, payload=payload or {}, fields=fields or {})

        @mcp.tool()
        def task_transfer(
            profile: str = DEFAULT_PROFILE,
            app_key: str = "",
            record_id: int = 0,
            payload: dict[str, Any] | None = None,
            fields: dict[str, Any] | None = None,
        ) -> dict[str, Any]:
            return self.task_transfer(profile=profile, app_key=app_key, record_id=record_id, payload=payload or {}, fields=fields or {})

        @mcp.tool()
        def task_transfer_candidates(
            profile: str = DEFAULT_PROFILE,
            app_key: str = "",
            record_id: int = 0,
            page_size: int = 20,
            page_num: int = 1,
            workflow_node_id: int = 0,
            keyword: str | None = None,
        ) -> dict[str, Any]:
            return self.task_transfer_candidates(
                profile=profile,
                app_key=app_key,
                record_id=record_id,
                page_size=page_size,
                page_num=page_num,
                workflow_node_id=workflow_node_id,
                keyword=keyword,
            )

    def record_comment_write(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any]) -> dict[str, Any]:
        """执行记录相关逻辑。"""
        raw = self.record_comment_add(profile=profile, app_key=app_key, apply_id=record_id, payload=payload)
        return self._public_action_response(
            raw,
            action="record_comment_write",
            resource={"app_key": app_key, "record_id": record_id},
            selection={},
        )

    def record_comment_mentions(
        self,
        *,
        profile: str,
        app_key: str,
        record_id: int,
        page_size: int = 20,
        page_num: int = 1,
        list_type: int | None = None,
        keyword: str | None = None,
    ) -> dict[str, Any]:
        """执行记录相关逻辑。"""
        selection = {"app_key": app_key, "record_id": record_id, "list_type": list_type, "keyword": keyword}
        try:
            raw = self.record_comment_mention_candidates(
                profile=profile,
                app_key=app_key,
                apply_id=record_id,
                page_size=page_size,
                page_num=page_num,
                list_type=list_type,
                keyword=keyword,
            )
        except RuntimeError as exc:
            if not _is_optional_approval_runtime_error(exc):
                raise
            return self._runtime_error_as_auxiliary_result(
                exc,
                error_code="RECORD_COMMENT_MENTIONS_UNAVAILABLE",
                selection=selection,
                fallback_hint="Mention candidates are unavailable in this permission context; write a plain comment or retry without mentions.",
            )
        items = _approval_page_items(raw.get("page"))
        return self._public_page_response(
            raw,
            items=items,
            pagination={
                "page": page_num,
                "page_size": page_size,
                "returned_items": len(items),
                "page_amount": _approval_page_amount(raw.get("page")),
                "reported_total": _approval_page_total(raw.get("page")),
            },
            selection=selection,
        )

    def task_approve(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any], fields: dict[str, Any] | None = None) -> dict[str, Any]:
        """执行任务相关逻辑。"""
        if fields:
            return self._delegate_task_action(
                profile=profile,
                app_key=app_key,
                record_id=record_id,
                action="approve",
                payload=payload,
                fields=fields,
                public_action="task_approve",
            )
        raw = self.record_approve(profile=profile, app_key=app_key, apply_id=record_id, payload=payload)
        return self._public_action_response(
            raw,
            action="task_approve",
            resource={"app_key": app_key, "record_id": record_id},
            selection={},
            human_review=True,
        )

    def task_reject(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any], fields: dict[str, Any] | None = None) -> dict[str, Any]:
        """执行任务相关逻辑。"""
        if fields:
            return self._delegate_task_action(
                profile=profile,
                app_key=app_key,
                record_id=record_id,
                action="reject",
                payload=payload,
                fields=fields,
                public_action="task_reject",
            )
        raw = self.record_reject(profile=profile, app_key=app_key, apply_id=record_id, payload=payload)
        return self._public_action_response(
            raw,
            action="task_reject",
            resource={"app_key": app_key, "record_id": record_id},
            selection={},
            human_review=True,
        )

    def task_rollback_candidates(self, *, profile: str, app_key: str, record_id: int, workflow_node_id: int) -> dict[str, Any]:
        """执行任务相关逻辑。"""
        selection = {"app_key": app_key, "record_id": record_id, "workflow_node_id": workflow_node_id}
        try:
            raw = self.record_rollback_candidates(profile=profile, app_key=app_key, apply_id=record_id, audit_node_id=workflow_node_id)
        except RuntimeError as exc:
            if not _is_optional_approval_runtime_error(exc):
                raise
            return self._runtime_error_as_auxiliary_result(
                exc,
                error_code="TASK_ROLLBACK_CANDIDATES_UNAVAILABLE",
                selection=selection,
                fallback_hint="Rollback candidates are unavailable in this permission context; use task get for current context or retry with a valid actionable node.",
            )
        items = _approval_page_items(raw.get("result"))
        return self._public_page_response(
            raw,
            items=items,
            pagination={"returned_items": len(items)},
            selection=selection,
        )

    def task_rollback(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any], fields: dict[str, Any] | None = None) -> dict[str, Any]:
        """执行任务相关逻辑。"""
        if fields:
            return self._delegate_task_action(
                profile=profile,
                app_key=app_key,
                record_id=record_id,
                action="rollback",
                payload=payload,
                fields=fields,
                public_action="task_rollback",
            )
        raw = self.record_rollback(profile=profile, app_key=app_key, apply_id=record_id, payload=payload)
        return self._public_action_response(
            raw,
            action="task_rollback",
            resource={"app_key": app_key, "record_id": record_id},
            selection={},
            human_review=True,
        )

    def task_transfer(self, *, profile: str, app_key: str, record_id: int, payload: dict[str, Any], fields: dict[str, Any] | None = None) -> dict[str, Any]:
        """执行任务相关逻辑。"""
        if fields:
            return self._delegate_task_action(
                profile=profile,
                app_key=app_key,
                record_id=record_id,
                action="transfer",
                payload=payload,
                fields=fields,
                public_action="task_transfer",
            )
        self._raise_if_self_transfer(profile=profile, payload=payload)
        raw = self.record_transfer(profile=profile, app_key=app_key, apply_id=record_id, payload=payload)
        return self._public_action_response(
            raw,
            action="task_transfer",
            resource={"app_key": app_key, "record_id": record_id},
            selection={},
            human_review=True,
        )

    def task_save_only(
        self,
        *,
        profile: str,
        app_key: str,
        record_id: int,
        workflow_node_id: int,
        fields: dict[str, Any],
    ) -> dict[str, Any]:
        """执行任务相关逻辑。"""
        return self._delegate_task_action(
            profile=profile,
            app_key=app_key,
            record_id=record_id,
            action="save_only",
            payload={},
            fields=fields,
            public_action="task_save_only",
            workflow_node_id=workflow_node_id,
        )

    def task_transfer_candidates(
        self,
        *,
        profile: str,
        app_key: str,
        record_id: int,
        page_size: int = 20,
        page_num: int = 1,
        workflow_node_id: int = 0,
        keyword: str | None = None,
    ) -> dict[str, Any]:
        """执行任务相关逻辑。"""
        selection = {"app_key": app_key, "record_id": record_id, "workflow_node_id": workflow_node_id, "keyword": keyword}
        try:
            raw = self.record_transfer_candidates(
                profile=profile,
                app_key=app_key,
                apply_id=record_id,
                page_size=page_size,
                page_num=page_num,
                audit_node_id=workflow_node_id,
                keyword=keyword,
            )
        except RuntimeError as exc:
            if not _is_optional_approval_runtime_error(exc):
                raise
            return self._runtime_error_as_auxiliary_result(
                exc,
                error_code="TASK_TRANSFER_CANDIDATES_UNAVAILABLE",
                selection=selection,
                fallback_hint="Transfer candidates are unavailable in this permission context; use task get for current context or retry with a valid actionable node.",
            )
        original_items = _approval_page_items(raw.get("page"))
        items = self._filter_self_transfer_candidates(profile=profile, items=original_items)
        filtered_count = max(len(original_items) - len(items), 0)
        return self._public_page_response(
            raw,
            items=items,
            pagination={
                "page": page_num,
                "page_size": page_size,
                "returned_items": len(items),
                "page_amount": _approval_page_amount(raw.get("page")),
                "reported_total": max(_approval_page_total(raw.get("page")) - filtered_count, 0),
            },
            selection=selection,
        )

    def record_comment_add(self, *, profile: str, app_key: str, apply_id: int, payload: dict[str, Any]) -> dict[str, Any]:
        """执行记录相关逻辑。"""
        self._require_app_and_apply(app_key, apply_id)
        self._validate_comment_payload(payload)

        def runner(session_profile, context):
            result = self.backend.request("POST", context, f"/app/{app_key}/apply/{apply_id}/comment", json_body=payload)
            return {
                "profile": profile,
                "ws_id": session_profile.selected_ws_id,
                "app_key": app_key,
                "apply_id": apply_id,
                "result": result,
                "request_route": self._request_route_payload(context),
            }

        return self._run(profile, runner, tool_name='新增评论')

    def record_comment_list(self, *, profile: str, app_key: str, apply_id: int, page_size: int = 20, list_type: int | None = None, page_num: int | None = 1) -> dict[str, Any]:
        """执行记录相关逻辑。"""
        self._require_app_and_apply(app_key, apply_id)

        def runner(session_profile, context):
            params: dict[str, Any] = {"pageSize": page_size}
            if list_type is not None:
                params["listType"] = list_type
            if page_num is not None:
                params["pageNum"] = page_num
            result = self.backend.request("GET", context, f"/app/{app_key}/apply/{apply_id}/comment", params=params)
            return {
                "profile": profile,
                "ws_id": session_profile.selected_ws_id,
                "app_key": app_key,
                "apply_id": apply_id,
                "list_type": list_type,
                "list_type_label": get_record_list_type_label(list_type),
                "page": result,
                "request_route": self._request_route_payload(context),
            }

        raw = self._run(profile, runner, tool_name='评论列表')
        items = _approval_page_items(raw.get("page"))
        return self._public_page_response(
            raw,
            items=items,
            pagination={
                "page": page_num,
                "page_size": page_size,
                "returned_items": len(items),
                "page_amount": _approval_page_amount(raw.get("page")),
                "reported_total": _approval_page_total(raw.get("page")),
            },
            selection={"app_key": app_key, "record_id": apply_id, "list_type": list_type},
        )

    def record_comment_mention_candidates(
        self,
        *,
        profile: str,
        app_key: str,
        apply_id: int,
        page_size: int = 20,
        page_num: int = 1,
        list_type: int | None = None,
        keyword: str | None = None,
    ) -> dict[str, Any]:
        """执行记录相关逻辑。"""
        self._require_app_and_apply(app_key, apply_id)

        def runner(session_profile, context):
            params: dict[str, Any] = {"pageSize": page_size, "pageNum": page_num}
            if list_type is not None:
                params["listType"] = list_type
            if keyword:
                params["keyword"] = keyword
            result = self.backend.request("GET", context, f"/app/{app_key}/apply/{apply_id}/comment/member", params=params)
            return {
                "profile": profile,
                "ws_id": session_profile.selected_ws_id,
                "app_key": app_key,
                "apply_id": apply_id,
                "list_type": list_type,
                "list_type_label": get_record_list_type_label(list_type),
                "page": result,
                "request_route": self._request_route_payload(context),
            }

        return self._run(profile, runner, tool_name='评论提及候选')

    def record_comment_mark_read(self, *, profile: str, app_key: str, apply_id: int) -> dict[str, Any]:
        """执行记录相关逻辑。"""
        self._require_app_and_apply(app_key, apply_id)

        def runner(session_profile, context):
            result = self.backend.request("POST", context, f"/app/{app_key}/apply/{apply_id}/comment/read")
            return {
                "profile": profile,
                "ws_id": session_profile.selected_ws_id,
                "app_key": app_key,
                "apply_id": apply_id,
                "result": result,
                "request_route": self._request_route_payload(context),
            }

        raw = self._run(profile, runner, tool_name='评论标记已读')
        return self._public_action_response(
            raw,
            action="record_comment_mark_read",
            resource={"app_key": app_key, "record_id": apply_id},
            selection={},
        )

    def record_comment_stats(self, *, profile: str, app_key: str, apply_id: int) -> dict[str, Any]:
        """执行记录相关逻辑。"""
        self._require_app_and_apply(app_key, apply_id)

        def runner(session_profile, context):
            result = self.backend.request("GET", context, f"/app/{app_key}/apply/{apply_id}/comment/statistic")
            return {
                "profile": profile,
                "ws_id": session_profile.selected_ws_id,
                "app_key": app_key,
                "apply_id": apply_id,
                "result": result,
                "request_route": self._request_route_payload(context),
            }

        return self._run(profile, runner, tool_name='评论统计')

    def record_approve(self, *, profile: str, app_key: str, apply_id: int, payload: dict[str, Any]) -> dict[str, Any]:
        """执行记录相关逻辑。"""
        self._require_app_and_apply(app_key, apply_id)
        body = self._require_dict(payload)

        def runner(session_profile, context):
            approval_body = self._normalize_approval_payload(profile, context, app_key, apply_id, body)
            result = self.backend.request("POST", context, "/workflow/engine/approval/approve", json_body=approval_body)
            return {
                "profile": profile,
                "ws_id": session_profile.selected_ws_id,
                "app_key": app_key,
                "apply_id": apply_id,
                "form_id": approval_body["formId"],
                "node_id": approval_body["nodeId"],
                "result": result,
                "request_route": self._request_route_payload(context),
            }

        return self._run(profile, runner, tool_name='记录通过')

    def record_reject(self, *, profile: str, app_key: str, apply_id: int, payload: dict[str, Any]) -> dict[str, Any]:
        """执行记录相关逻辑。"""
        self._require_app_and_apply(app_key, apply_id)
        body = self._require_dict(payload)

        def runner(session_profile, context):
            approval_body = self._normalize_approval_payload(profile, context, app_key, apply_id, body)
            result = self.backend.request("POST", context, "/workflow/engine/approval/reject", json_body=approval_body)
            return {
                "profile": profile,
                "ws_id": session_profile.selected_ws_id,
                "app_key": app_key,
                "apply_id": apply_id,
                "form_id": approval_body["formId"],
                "node_id": approval_body["nodeId"],
                "result": result,
                "request_route": self._request_route_payload(context),
            }

        return self._run(profile, runner, tool_name='记录拒绝')

    def record_rollback_candidates(self, *, profile: str, app_key: str, apply_id: int, audit_node_id: int) -> dict[str, Any]:
        """执行记录相关逻辑。"""
        self._require_app_and_apply(app_key, apply_id)
        if audit_node_id <= 0:
            raise_tool_error(QingflowApiError.config_error("audit_node_id must be positive"))

        def runner(session_profile, context):
            result = self.backend.request(
                "GET",
                context,
                f"/app/{app_key}/apply/{apply_id}/revertNode",
                params={"auditNodeId": audit_node_id},
            )
            return {
                "profile": profile,
                "ws_id": session_profile.selected_ws_id,
                "app_key": app_key,
                "apply_id": apply_id,
                "result": result,
                "request_route": self._request_route_payload(context),
            }

        return self._run(profile, runner, tool_name='记录退回候选')

    def record_rollback(self, *, profile: str, app_key: str, apply_id: int, payload: dict[str, Any]) -> dict[str, Any]:
        """执行记录相关逻辑。"""
        self._require_app_and_apply(app_key, apply_id)
        body = self._require_dict(payload)
        self._validate_audit_payload(body)

        def runner(session_profile, context):
            result = self.backend.request("POST", context, f"/app/{app_key}/apply/{apply_id}/rollback", json_body=body)
            return {
                "profile": profile,
                "ws_id": session_profile.selected_ws_id,
                "app_key": app_key,
                "apply_id": apply_id,
                "result": result,
                "request_route": self._request_route_payload(context),
            }

        return self._run(profile, runner, tool_name='记录退回')

    def record_transfer(self, *, profile: str, app_key: str, apply_id: int, payload: dict[str, Any]) -> dict[str, Any]:
        """执行记录相关逻辑。"""
        self._require_app_and_apply(app_key, apply_id)
        body = self._require_dict(payload)
        self._validate_audit_payload(body, require_uid=True)

        def runner(session_profile, context):
            result = self.backend.request("POST", context, f"/app/{app_key}/apply/{apply_id}/transfer", json_body=body)
            return {
                "profile": profile,
                "ws_id": session_profile.selected_ws_id,
                "app_key": app_key,
                "apply_id": apply_id,
                "result": result,
                "request_route": self._request_route_payload(context),
            }

        return self._run(profile, runner, tool_name='记录转交')

    def record_transfer_candidates(
        self,
        *,
        profile: str,
        app_key: str,
        apply_id: int,
        page_size: int = 20,
        page_num: int = 1,
        audit_node_id: int = 0,
        keyword: str | None = None,
    ) -> dict[str, Any]:
        """执行记录相关逻辑。"""
        self._require_app_and_apply(app_key, apply_id)
        if audit_node_id <= 0:
            raise_tool_error(QingflowApiError.config_error("audit_node_id must be positive"))

        def runner(session_profile, context):
            params: dict[str, Any] = {"pageSize": page_size, "pageNum": page_num, "auditNodeId": audit_node_id}
            if keyword:
                params["keyword"] = keyword
            result = self.backend.request("GET", context, f"/app/{app_key}/apply/{apply_id}/transfer/member", params=params)
            return {
                "profile": profile,
                "ws_id": session_profile.selected_ws_id,
                "app_key": app_key,
                "apply_id": apply_id,
                "page": result,
                "request_route": self._request_route_payload(context),
            }

        return self._run(profile, runner, tool_name='记录转交候选')

    def record_reassign_get(self, *, profile: str, app_key: str, apply_id: int) -> dict[str, Any]:
        """执行记录相关逻辑。"""
        self._require_app_and_apply(app_key, apply_id)

        def runner(session_profile, context):
            result = self.backend.request("GET", context, f"/app/{app_key}/apply/{apply_id}/reassign")
            return {
                "profile": profile,
                "ws_id": session_profile.selected_ws_id,
                "app_key": app_key,
                "apply_id": apply_id,
                "result": result,
                "request_route": self._request_route_payload(context),
            }

        return self._run(profile, runner, tool_name='记录改派信息')

    def record_reassign(self, *, profile: str, app_key: str, apply_id: int, payload: dict[str, Any]) -> dict[str, Any]:
        """执行记录相关逻辑。"""
        self._require_app_and_apply(app_key, apply_id)
        body = self._require_dict(payload)

        def runner(session_profile, context):
            result = self.backend.request("POST", context, f"/app/{app_key}/apply/{apply_id}/reassign", json_body=body)
            return {
                "profile": profile,
                "ws_id": session_profile.selected_ws_id,
                "app_key": app_key,
                "apply_id": apply_id,
                "result": result,
                "request_route": self._request_route_payload(context),
            }

        return self._run(profile, runner, tool_name='记录改派')

    def record_countersign_candidates(
        self,
        *,
        profile: str,
        app_key: str,
        apply_id: int,
        page_size: int = 20,
        page_num: int = 1,
        audit_node_id: int = 0,
        search_key: str | None = None,
    ) -> dict[str, Any]:
        """执行记录相关逻辑。"""
        self._require_app_and_apply(app_key, apply_id)
        if audit_node_id <= 0:
            raise_tool_error(QingflowApiError.config_error("audit_node_id must be positive"))

        def runner(session_profile, context):
            params: dict[str, Any] = {"pageSize": page_size, "pageNum": page_num, "auditNodeId": audit_node_id}
            if search_key:
                params["searchKey"] = search_key
            result = self.backend.request("GET", context, f"/app/{app_key}/apply/{apply_id}/countersign/member", params=params)
            return {
                "profile": profile,
                "ws_id": session_profile.selected_ws_id,
                "app_key": app_key,
                "apply_id": apply_id,
                "page": result,
                "request_route": self._request_route_payload(context),
            }

        return self._run(profile, runner, tool_name='会签候选')

    def record_countersign(self, *, profile: str, app_key: str, apply_id: int, payload: dict[str, Any]) -> dict[str, Any]:
        """执行记录相关逻辑。"""
        self._require_app_and_apply(app_key, apply_id)
        body = self._require_dict(payload)
        self._validate_countersign_payload(body)

        def runner(session_profile, context):
            result = self.backend.request("POST", context, f"/app/{app_key}/apply/{apply_id}/countersign", json_body=body)
            return {
                "profile": profile,
                "ws_id": session_profile.selected_ws_id,
                "app_key": app_key,
                "apply_id": apply_id,
                "result": result,
                "request_route": self._request_route_payload(context),
            }

        return self._run(profile, runner, tool_name='会签')

    def _request_route_payload(self, context) -> JSONObject:  # type: ignore[no-untyped-def]
        """执行内部辅助逻辑。"""
        describe_route = getattr(self.backend, "describe_route", None)
        if callable(describe_route):
            payload = describe_route(context)
            if isinstance(payload, dict):
                return payload
        return {
            "base_url": getattr(context, "base_url", None),
            "qf_version": getattr(context, "qf_version", None),
            "qf_version_source": getattr(context, "qf_version_source", None) or ("context" if getattr(context, "qf_version", None) else "unknown"),
        }

    def _require_app_and_apply(self, app_key: str, apply_id: int) -> None:
        """执行内部辅助逻辑。"""
        if not app_key:
            raise_tool_error(QingflowApiError.config_error("app_key is required"))
        if apply_id <= 0:
            raise_tool_error(QingflowApiError.config_error("apply_id must be positive"))

    def _validate_comment_payload(self, payload: dict[str, Any]) -> None:
        """执行内部辅助逻辑。"""
        comment_detail = payload.get("commentDetail")
        if not isinstance(comment_detail, dict):
            raise_tool_error(QingflowApiError.config_error("payload.commentDetail must be an object"))
        if not comment_detail.get("commentMsg"):
            raise_tool_error(QingflowApiError.config_error("payload.commentDetail.commentMsg is required"))

    def _normalize_approval_payload(self, profile: str, context, app_key: str, apply_id: int, payload: dict[str, Any]) -> JSONObject:  # type: ignore[no-untyped-def]
        """执行内部辅助逻辑。"""
        body: JSONObject = dict(payload)
        self._normalize_alias(body, "auditFeedback", "audit_feedback")
        self._normalize_alias(body, "uploadFileSize", "upload_file_size")
        self._normalize_alias(body, "handSignImageUrl", "hand_sign_image_url")
        self._normalize_alias(body, "beingSaveSignature", "being_save_signature")
        self._normalize_alias(body, "applyId", "apply_id")
        self._normalize_alias(body, "formId", "form_id")

        node_id = self._extract_node_id(body)
        body["nodeId"] = self._resolve_actionable_node_id(context, app_key, apply_id, node_id)
        body["applyId"] = self._match_or_fill_int(body, field_name="applyId", expected_value=apply_id)
        current_detail: dict[str, Any] | None = None
        if body.get("answers") is None or body.get("formId") is None:
            current_detail = self._fetch_current_todo_detail(context, app_key, apply_id, body["nodeId"])
        body["formId"] = self._resolve_form_id(
            profile,
            context,
            app_key,
            explicit_form_id=body.get("formId"),
            current_detail=current_detail,
        )
        if body.get("answers") is None:
            body["answers"] = self._extract_current_todo_answers(current_detail, apply_id=apply_id, node_id=body["nodeId"])

        self._validate_approval_payload(body)
        return body

    def _extract_node_id(self, payload: JSONObject) -> int:
        """执行内部辅助逻辑。"""
        node_id = payload.get("nodeId")
        audit_node_id = payload.pop("auditNodeId", None)
        if node_id is None:
            node_id = audit_node_id
        elif audit_node_id is not None and node_id != audit_node_id:
            raise_tool_error(QingflowApiError.config_error("payload.nodeId and payload.auditNodeId must match when both are provided"))
        if not isinstance(node_id, int) or node_id <= 0:
            raise_tool_error(QingflowApiError.config_error("payload.nodeId or payload.auditNodeId must be a positive integer"))
        return node_id

    def _resolve_form_id(
        self,
        profile: str,
        context,
        app_key: str,
        *,
        explicit_form_id: Any | None,
        current_detail: dict[str, Any] | None = None,
    ) -> int:  # type: ignore[no-untyped-def]
        """执行内部辅助逻辑。"""
        detail_form_id = self._extract_form_id_from_current_detail(current_detail)
        if explicit_form_id is not None:
            if not isinstance(explicit_form_id, int) or explicit_form_id <= 0:
                raise_tool_error(QingflowApiError.config_error("payload.formId must be a positive integer"))
            try:
                form_id = detail_form_id or self._get_form_id(profile, context, app_key)
            except QingflowApiError as exc:
                if not _is_optional_approval_precheck_error(exc):
                    raise
                self._form_id_cache[f"{profile}:{app_key}"] = explicit_form_id
                return explicit_form_id
            if form_id != explicit_form_id:
                raise_tool_error(
                    QingflowApiError.config_error(
                        f"payload.formId={explicit_form_id} does not match app_key '{app_key}' formId={form_id}"
                    )
                )
            return explicit_form_id
        if detail_form_id is not None:
            self._form_id_cache[f"{profile}:{app_key}"] = detail_form_id
            return detail_form_id
        return self._get_form_id(profile, context, app_key)

    def _get_form_id(self, profile: str, context, app_key: str) -> int:  # type: ignore[no-untyped-def]
        """执行内部辅助逻辑。"""
        cache_key = f"{profile}:{app_key}"
        cached = self._form_id_cache.get(cache_key)
        if cached is not None:
            return cached
        result = self.backend.request("GET", context, f"/app/{app_key}/baseInfo")
        form_id = result.get("formId") if isinstance(result, dict) else None
        if not isinstance(form_id, int) or form_id <= 0:
            raise_tool_error(QingflowApiError.config_error(f"cannot resolve formId for app_key '{app_key}'"))
        self._form_id_cache[cache_key] = form_id
        return form_id

    def _match_or_fill_int(self, payload: JSONObject, *, field_name: str, expected_value: int) -> int:
        """执行内部辅助逻辑。"""
        current = payload.get(field_name)
        if current is None:
            return expected_value
        if not isinstance(current, int) or current <= 0:
            raise_tool_error(QingflowApiError.config_error(f"payload.{field_name} must be a positive integer"))
        if current != expected_value:
            raise_tool_error(QingflowApiError.config_error(f"payload.{field_name}={current} does not match apply_id={expected_value}"))
        return current

    def _normalize_alias(self, payload: JSONObject, canonical_key: str, alias_key: str) -> None:
        """执行内部辅助逻辑。"""
        alias_value = payload.pop(alias_key, None)
        if canonical_key not in payload and alias_value is not None:
            payload[canonical_key] = alias_value
        elif alias_value is not None and payload.get(canonical_key) != alias_value:
            raise_tool_error(QingflowApiError.config_error(f"payload.{canonical_key} and payload.{alias_key} must match when both are provided"))

    def _resolve_actionable_node_id(self, context, app_key: str, apply_id: int, node_id: int) -> int:  # type: ignore[no-untyped-def]
        """执行内部辅助逻辑。"""
        try:
            infos = self.backend.request(
                "GET",
                context,
                f"/app/{app_key}/apply/{apply_id}/auditInfo",
                params={"type": 1},
            )
        except QingflowApiError as exc:
            if not _is_optional_approval_precheck_error(exc):
                raise
            self._fetch_current_todo_detail(context, app_key, apply_id, node_id)
            return node_id
        if not isinstance(infos, list) or not infos:
            raise_tool_error(
                QingflowApiError.config_error(
                    f"apply_id={apply_id} is not currently actionable for the logged-in user in todo list"
                )
            )
        actionable_node_ids = {
            candidate
            for item in infos
            if isinstance(item, dict)
            for candidate in (item.get("auditNodeId"), item.get("nodeId"))
            if isinstance(candidate, int) and candidate > 0
        }
        if node_id not in actionable_node_ids:
            raise_tool_error(
                QingflowApiError.config_error(
                    f"payload.nodeId={node_id} is not an actionable todo node for apply_id={apply_id}"
                )
            )
        return node_id

    def _fetch_current_todo_detail(self, context, app_key: str, apply_id: int, node_id: int) -> dict[str, Any]:  # type: ignore[no-untyped-def]
        """执行内部辅助逻辑。"""
        detail = self.backend.request(
            "GET",
            context,
            f"/app/{app_key}/apply/{apply_id}",
            params={"role": 3, "listType": 1, "auditNodeId": node_id},
        )
        if not isinstance(detail, dict):
            raise_tool_error(
                QingflowApiError.config_error(
                    f"cannot resolve current todo detail for apply_id={apply_id} nodeId={node_id}"
                )
            )
        return detail

    def _fetch_current_todo_answers(self, context, app_key: str, apply_id: int, node_id: int) -> list[dict[str, Any]]:  # type: ignore[no-untyped-def]
        """执行内部辅助逻辑。"""
        detail = self._fetch_current_todo_detail(context, app_key, apply_id, node_id)
        return self._extract_current_todo_answers(detail, apply_id=apply_id, node_id=node_id)

    def _extract_current_todo_answers(self, detail: dict[str, Any] | None, *, apply_id: int, node_id: int) -> list[dict[str, Any]]:
        """执行内部辅助逻辑。"""
        answers = detail.get("answers") if isinstance(detail, dict) else None
        if not isinstance(answers, list):
            raise_tool_error(
                QingflowApiError.config_error(
                    f"cannot resolve current answers for apply_id={apply_id} nodeId={node_id}"
                )
            )
        normalized_answers: list[dict[str, Any]] = []
        for item in answers:
            if isinstance(item, dict):
                normalized_answers.append(dict(item))
        return normalized_answers

    def _extract_form_id_from_current_detail(self, detail: dict[str, Any] | None) -> int | None:
        """执行内部辅助逻辑。"""
        if not isinstance(detail, dict):
            return None
        for key in ("formId", "form_id"):
            value = detail.get(key)
            if isinstance(value, int) and value > 0:
                return value
        form = detail.get("form")
        if isinstance(form, dict):
            value = form.get("formId") or form.get("form_id")
            if isinstance(value, int) and value > 0:
                return value
        return None

    def _validate_approval_payload(self, payload: dict[str, Any]) -> None:
        """执行内部辅助逻辑。"""
        self._reject_unsupported_fields(payload)
        if not isinstance(payload.get("formId"), int) or payload["formId"] <= 0:
            raise_tool_error(QingflowApiError.config_error("payload.formId must be a positive integer"))
        if not isinstance(payload.get("applyId"), int) or payload["applyId"] <= 0:
            raise_tool_error(QingflowApiError.config_error("payload.applyId must be a positive integer"))
        if not isinstance(payload.get("nodeId"), int) or payload["nodeId"] <= 0:
            raise_tool_error(QingflowApiError.config_error("payload.nodeId must be a positive integer"))
        answers = payload.get("answers")
        if answers is not None and not isinstance(answers, list):
            raise_tool_error(QingflowApiError.config_error("payload.answers must be an array when provided"))

    def _validate_audit_payload(self, payload: dict[str, Any], *, require_uid: bool = False) -> None:
        """执行内部辅助逻辑。"""
        self._reject_unsupported_fields(payload)
        if require_uid and not payload.get("uid"):
            raise_tool_error(QingflowApiError.config_error("payload.uid is required"))

    def _validate_countersign_payload(self, payload: dict[str, Any]) -> None:
        """执行内部辅助逻辑。"""
        self._reject_unsupported_fields(payload)
        members = payload.get("countersignMembers")
        if not isinstance(members, list) or not members:
            raise_tool_error(QingflowApiError.config_error("payload.countersignMembers must be a non-empty array"))

    def _reject_unsupported_fields(self, payload: dict[str, Any]) -> None:
        """执行内部辅助逻辑。"""
        if payload.get("handSignImageUrl"):
            raise_tool_error(QingflowApiError.not_supported("NOT_SUPPORTED_IN_V1: handSignImageUrl is not supported"))

    def _extract_transfer_target_uid(self, payload: dict[str, Any]) -> int | None:
        """执行内部辅助逻辑。"""
        for key in ("uid", "target_member_id", "targetMemberId"):
            value = payload.get(key)
            if isinstance(value, int) and value > 0:
                return value
        return None

    def _raise_if_self_transfer(self, *, profile: str, payload: dict[str, Any]) -> None:
        """执行内部辅助逻辑。"""
        target_uid = self._extract_transfer_target_uid(payload)
        if target_uid is None:
            return
        session_profile = self.sessions.get_profile(profile)
        if session_profile is not None and target_uid == session_profile.uid:
            raise_tool_error(
                QingflowApiError.config_error(
                    "task transfer does not support transferring to the current user; choose another transfer member"
                )
            )

    def _filter_self_transfer_candidates(self, *, profile: str, items: list[dict[str, Any]]) -> list[dict[str, Any]]:
        """执行内部辅助逻辑。"""
        session_profile = self.sessions.get_profile(profile)
        if session_profile is None:
            return items
        current_uid = session_profile.uid
        return [item for item in items if item.get("uid") != current_uid]

    def _delegate_task_action(
        self,
        *,
        profile: str,
        app_key: str,
        record_id: int,
        action: str,
        payload: dict[str, Any],
        fields: dict[str, Any],
        public_action: str,
        workflow_node_id: int | None = None,
    ) -> dict[str, Any]:
        """执行内部辅助逻辑。"""
        from .task_context_tools import TaskContextTools

        node_id = workflow_node_id
        if node_id is None:
            node_payload = dict(payload or {})
            node_id = self._extract_node_id(node_payload)
        delegated = TaskContextTools(self.sessions, self.backend)._task_action_execute_with_locator(
            profile=profile,
            app_key=app_key,
            record_id=record_id,
            workflow_node_id=node_id,
            action=action,
            payload=payload or {},
            fields=fields or {},
        )
        response = dict(delegated)
        data = response.get("data")
        if isinstance(data, dict):
            payload_data = dict(data)
            payload_data["action"] = public_action
            response["data"] = payload_data
        return response

    def _public_page_response(
        self,
        raw: dict[str, Any],
        *,
        items: list[dict[str, Any]],
        pagination: dict[str, Any],
        selection: dict[str, Any],
    ) -> dict[str, Any]:
        """执行内部辅助逻辑。"""
        response = dict(raw)
        response["ok"] = bool(raw.get("ok", True))
        response["warnings"] = []
        response["output_profile"] = "normal"
        response["data"] = {
            "items": items,
            "pagination": pagination,
            "selection": selection,
        }
        return response

    def _runtime_error_as_auxiliary_result(
        self,
        error: RuntimeError,
        *,
        error_code: str,
        selection: dict[str, Any],
        fallback_hint: str,
    ) -> dict[str, Any]:
        """Return a structured failure for optional approval/comment helpers."""
        try:
            payload = json.loads(str(error))
        except json.JSONDecodeError:
            payload = {"message": str(error)}
        details = payload.get("details") if isinstance(payload.get("details"), dict) else {}
        warning: dict[str, Any] = {
            "code": error_code,
            "message": fallback_hint,
        }
        for key in ("category", "backend_code", "request_id", "http_status"):
            if payload.get(key) is not None:
                warning[key] = payload.get(key)
        response: dict[str, Any] = {
            "ok": False,
            "status": "failed",
            "error_code": details.get("error_code") or error_code,
            "message": payload.get("message") or str(error),
            "warnings": [warning],
            "output_profile": "normal",
            "data": {
                "items": [],
                "pagination": {"returned_items": 0},
                "selection": selection,
                "fallback_hint": fallback_hint,
            },
        }
        for key in ("category", "backend_code", "request_id", "http_status"):
            if payload.get(key) is not None:
                response[key] = payload.get(key)
        if details:
            response["details"] = details
        return response

    def _public_action_response(
        self,
        raw: dict[str, Any],
        *,
        action: str,
        resource: dict[str, Any],
        selection: dict[str, Any],
        human_review: bool = False,
    ) -> dict[str, Any]:
        """执行内部辅助逻辑。"""
        response = dict(raw)
        response["ok"] = bool(raw.get("ok", True))
        response["warnings"] = []
        response["output_profile"] = "normal"
        response["data"] = {
            "action": action,
            "resource": resource,
            "selection": selection,
            "result": raw.get("result"),
            "human_review": human_review,
        }
        return response

    def _request_route_payload(self, context) -> JSONObject:  # type: ignore[no-untyped-def]
        """执行内部辅助逻辑。"""
        describe_route = getattr(self.backend, "describe_route", None)
        if callable(describe_route):
            payload = describe_route(context)
            if isinstance(payload, dict):
                return payload
        return {
            "base_url": context.base_url,
            "qf_version": context.qf_version,
            "qf_version_source": getattr(context, "qf_version_source", None) or ("context" if getattr(context, "qf_version", None) else "unknown"),
        }


def _approval_page_items(payload: Any) -> list[dict[str, Any]]:
    if isinstance(payload, list):
        return [item for item in payload if isinstance(item, dict)]
    if not isinstance(payload, dict):
        return []
    for key in ("list", "items", "rows", "result"):
        value = payload.get(key)
        if isinstance(value, list):
            return [item for item in value if isinstance(item, dict)]
    for container_key in ("data", "page"):
        nested = payload.get(container_key)
        if isinstance(nested, dict):
            nested_items = _approval_page_items(nested)
            if nested_items:
                return nested_items
    return []


def _approval_page_amount(payload: Any) -> Any:
    if isinstance(payload, dict):
        return payload.get("pageAmount", payload.get("page_amount"))
    return None


def _approval_page_total(payload: Any) -> Any:
    if isinstance(payload, dict):
        return payload.get("total", payload.get("count"))
    return None


def _is_optional_approval_precheck_error(error: QingflowApiError) -> bool:
    if is_auth_like_error(error):
        return False
    backend_code = backend_code_int(error)
    return backend_code in {40002, 40027, 404} or error.http_status == 404


def _is_optional_approval_runtime_error(error: RuntimeError) -> bool:
    try:
        payload = json.loads(str(error))
    except json.JSONDecodeError:
        return False
    if not isinstance(payload, dict):
        return False
    if str(payload.get("category") or "").strip().lower() == "auth":
        return False
    if message_looks_like_invalid_token(payload.get("message")):
        return False
    if backend_code_value_int(payload.get("http_status")) == 401:
        return False
    return backend_code_value_int(payload.get("backend_code")) in {40002, 40027, 404} or backend_code_value_int(payload.get("http_status")) == 404
