"""
DataSource V2 - 状态管理 + Handler 编排层

DataSource 职责：
1. 注册和管理 Handler
2. 按依赖顺序加载数据
3. 执行同步（调用 Handler）
4. 管理节点状态流转（PENDING → IN_PROGRESS → SUCCESS/FAILED）
5. 处理异步任务轮询
6. 统一异常处理

DataSource 不负责：
- ❌ 后端 API 调用（由 Handler 负责）
- ❌ ID 映射（由 Strategy 负责）
- ❌ 业务逻辑（由 Strategy 负责）
"""
from __future__ import annotations

import asyncio
import logging
from typing import Any, List, Dict, Optional, TYPE_CHECKING
from abc import ABC
from pathlib import Path

if TYPE_CHECKING:
    from ..common.collection import DataCollection
    from .handler import NodeHandler
    from ..common.sync_node import SyncNode
    from ..common.types import SyncAction
    from .task_result import TaskStatus

from ..common.types import SyncStatus
from ..common.type_safety import get_pydantic_model_fields
from .task_result import TaskResult
from ..engine import (
    StateMachineConfig,
    StateMachineRuntime,
    e40_sync_execute,
)


logger = logging.getLogger(__name__)


class BaseDataSource(ABC):
    """
    数据源基类 V2（状态管理 + 编排层）
    
    核心流程：
    1. 注册 Handler：每个节点类型对应一个 Handler
    2. 加载数据：按依赖顺序调用 Handler.load()
    3. 执行同步：根据 node.action 调用 Handler 的 create/update/delete
    4. 状态管理：根据 TaskResult 更新 node.status
    5. 异步轮询：处理 IN_PROGRESS 节点
    
    示例：
        # 1. 初始化 DataSource
        datasource = MyDataSource()
        
        # 2. 添加 Handler
        datasource.add_handler(project_handler)
        datasource.add_handler(contract_handler)
        
        # 3. 设置 Collection
        datasource.set_collection(collection)
        
        # 4. 加载数据（按顺序）
        await datasource.load_all(order=["project", "contract"])
        
        # 5. 执行同步（按顺序）
        await datasource.sync_all(order=["project", "contract"])
    """
    
    def __init__(self, poll_max_retries: int = 1, poll_interval: float = 0.5):
        self._handlers: Dict[str, "NodeHandler"] = {}
        self._collection: Optional["DataCollection"] = None
        
        # 按节点类型统计信息
        # {"company": {"loaded": 10, "synced": 10, "success": 10, "failed": 0}, ...}
        self._stats: Dict[str, Dict[str, int]] = {}
        # 记录本次 sync 的动作统计（CREATE/UPDATE/DELETE/NONE）
        self._action_summary: Dict[str, Dict[str, Dict[str, int]]] = {}
        # 记录本次 sync 中的节点动作（用于异步完成后的统计）
        self._action_by_node_id: Dict[str, "SyncAction"] = {}
        self._poll_max_retries = max(1, poll_max_retries)
        self._poll_interval = max(0, poll_interval)
        self._sm_runtime: Optional[StateMachineRuntime] = None

    def _ensure_sm_runtime(self) -> StateMachineRuntime:
        if self._sm_runtime is None:
            cfg_path = Path(__file__).resolve().parents[1] / "config" / "node_state_machine.yaml"
            self._sm_runtime = StateMachineRuntime(StateMachineConfig.load(cfg_path))
        return self._sm_runtime

    def set_state_machine_runtime(self, runtime: StateMachineRuntime) -> None:
        """注入统一状态机 runtime（通常由 pipeline 负责）。"""
        self._sm_runtime = runtime

    def _apply_sync_execute_state(
        self,
        node: "SyncNode",
        *,
        handler_status: "TaskStatus",
        poll_timeout: bool = False,
        detail: str = "",
        action_override: Optional["SyncAction"] = None,
        result_data_id: Optional[str] = None,
    ) -> bool:
        action_for_event = action_override or node.action
        decision = e40_sync_execute(
            self._ensure_sm_runtime(),
            node=node,
            handler_result=handler_status.name,
            action=action_for_event.value,
            poll_timeout=poll_timeout,
            result_data_id=result_data_id,
        )
        if decision is None:
            if detail:
                node.append_log(
                    f"执行状态未变更: action={action_for_event.value}, result={handler_status.name}, detail={detail}"
                )
            return False
        if detail:
            node.append_log(
                f"执行状态写回: action={action_for_event.value}, result={handler_status.name}, detail={detail}"
            )
        return True

    def _compose_node_error(self, node: "SyncNode", *, source: str, detail: str) -> str:
        return (
            f"{source} | node={node.node_id} | type={node.node_type} | "
            f"action={node.action.value} | status={node.status.value} | detail={detail}"
        )

    def _on_node_loaded(self, handler: "NodeHandler", node: "SyncNode") -> None:
        """Datasource-specific hook when a loaded node is upserted into collection."""
        return

    async def _intercept_success_result(
        self,
        handler: "NodeHandler",
        node: "SyncNode",
        result: "TaskResult",
        async_tasks: Dict[str, str],
    ) -> bool:
        """Datasource-specific hook to intercept SUCCESS result handling.

        Return True if handled/consumed by subclass and no further default SUCCESS flow is needed.
        """
        return False

    def _on_in_progress_result(self, handler: "NodeHandler", node: "SyncNode", result: "TaskResult") -> None:
        """Datasource-specific hook after an IN_PROGRESS task is queued."""
        return

    def _on_poll_result(self, handler: "NodeHandler", node: "SyncNode", task_id: str, result: "TaskResult") -> None:
        """Datasource-specific hook after polling result is applied to node."""
        return

    def _on_failed_result(self, handler: "NodeHandler", node: "SyncNode", result: "TaskResult") -> None:
        """Datasource-specific hook after a FAILED result is applied to node."""
        return

    async def _handle_success_result(self, node: "SyncNode", result: "TaskResult") -> None:
        previous_data_id = node.data_id
        ok = self._apply_sync_execute_state(
            node,
            handler_status=result.status,
            detail="远程操作成功完成",
            result_data_id=result.data_id,
        )
        if not ok:
            raise RuntimeError(f"[{node.node_type}] SUCCESS result cannot map to state machine: node={node.node_id}")
        self._record_action_result(node, result.status)
        await self._apply_success_result(node, result.data_id, previous_data_id=previous_data_id)
        node.error = None

    async def _handle_failed_result(self, node: "SyncNode", result: "TaskResult") -> None:
        detail = result.error or "Unknown error"
        ok = self._apply_sync_execute_state(
            node,
            handler_status=result.status,
            detail=f"远程操作失败: {detail}",
        )
        if not ok:
            raise RuntimeError(f"[{node.node_type}] FAILED result cannot map to state machine: node={node.node_id}")
        self._record_action_result(node, result.status)
        node.error = self._compose_node_error(node, source="handler_failed", detail=detail)

    async def _handle_skipped_result(self, node: "SyncNode", result: "TaskResult") -> None:
        from .task_result import TaskStatus as _TaskStatus

        original_action = self._action_by_node_id.get(node.node_id)
        if original_action is None or original_action.value != "UPDATE":
            raise RuntimeError(
                f"[{node.node_type}] SKIPPED is only allowed for UPDATE action: node={node.node_id}, action={original_action.value if original_action is not None else None}"
            )

        ok = self._apply_sync_execute_state(
            node,
            handler_status=_TaskStatus.SKIPPED,
            detail="远程操作被跳过",
            action_override=original_action,
        )
        if not ok:
            raise RuntimeError(f"[{node.node_type}] SKIPPED result cannot map to state machine: node={node.node_id}")

        self._record_action_result(node, _TaskStatus.SKIPPED)
        if result.sync_log:
            node.append_log(f"同步跳过: {result.sync_log}")
        node.error = None

    async def _handle_in_progress_result(
        self,
        node: "SyncNode",
        result: "TaskResult",
        async_tasks: Dict[str, str],
    ) -> None:
        if not result.task_id:
            node.error = self._compose_node_error(
                node,
                source="handler_in_progress_invalid",
                detail="missing task_id",
            )
            node.append_log("异步任务返回无 task_id，已忽略")
            return

        if not result.node_id:
            node.error = self._compose_node_error(
                node,
                source="handler_in_progress_invalid",
                detail="missing node_id",
            )
            node.append_log("异步任务返回无 node_id，已忽略")
            return

        async_tasks[result.node_id] = result.task_id
        ok = self._apply_sync_execute_state(
            node,
            handler_status=result.status,
            detail=f"异步任务进行中: {result.task_id}",
        )
        if not ok:
            node.append_log(f"异步任务进行中（无状态变更）: {result.task_id}")

    def set_polling_config(self, max_retries: int, interval: float = 0.5) -> None:
        """设置异步轮询参数（默认仅轮询一次）"""
        self._poll_max_retries = max(1, max_retries)
        self._poll_interval = max(0, interval)
    
    def reset_stats(self) -> None:
        """重置统计信息"""
        self._stats = {}
        self._action_summary = {}
        self._action_by_node_id = {}
    
    def get_stats(self) -> Dict[str, Dict[str, int]]:
        """
        获取按节点类型的统计信息
        
        Returns:
            dict: {"company": {"loaded": 10, "synced": 10, "success": 10, "failed": 0}, ...}
        """
        return self._stats.copy()

    def get_action_summary(self) -> Dict[str, Dict[str, Dict[str, int]]]:
        """
        获取本次 sync 的动作统计（按节点类型）。

        Returns:
            dict: {
                "company": {
                    "create": {"total": 10, "success": 8, "failed": 2},
                    "update": {"total": 2, "success": 2, "failed": 0},
                    "delete": {"total": 0, "success": 0, "failed": 0},
                    "none": {"total": 5, "success": 0, "failed": 0},
                    "total": {"total": 17, "success": 10, "failed": 2},
                },
                ...
            }
        """
        return {k: {kk: vv.copy() for kk, vv in v.items()} for k, v in self._action_summary.items()}
    
    def _init_node_stats(self, node_type: str) -> None:
        """初始化节点类型的统计信息"""
        if node_type not in self._stats:
            self._stats[node_type] = {
                "loaded": 0,
                "synced": 0,
                "success": 0,
                "failed": 0,
            }
        if node_type not in self._action_summary:
            self._action_summary[node_type] = {
                "create": {"total": 0, "success": 0, "failed": 0, "skipped": 0},
                "update": {"total": 0, "success": 0, "failed": 0, "skipped": 0},
                "delete": {"total": 0, "success": 0, "failed": 0, "skipped": 0},
                "none": {"total": 0, "success": 0, "failed": 0, "skipped": 0},
                "total": {"total": 0, "success": 0, "failed": 0, "skipped": 0},
            }

    def _set_action_totals(self, node_type: str, create: int, update: int, delete: int, none: int) -> None:
        summary = self._action_summary[node_type]
        summary["create"]["total"] = create
        summary["update"]["total"] = update
        summary["delete"]["total"] = delete
        summary["none"]["total"] = none
        summary["total"]["total"] = create + update + delete + none

    def _record_action_result(self, node: "SyncNode", result_status: "TaskStatus") -> None:
        from ..common.types import SyncAction
        from .task_result import TaskStatus as _TaskStatus

        # 情况 A: 原始动作就是 NONE
        # 情况 B: Handler 运行时发现没差异，把 action 降级为了 NONE
        action = node.action
        
        # 获取最初锁定的动作（用于修正统计列）
        original_action = self._action_by_node_id.pop(node.node_id, SyncAction.NONE)

        summary = self._action_summary.get(node.node_type)
        if summary is None:
            return

        # 如果当前 action 是 NONE，说明这次“同步”被取消了
        if action == SyncAction.NONE:
            # 如果原始动作不是 NONE，说明发生了降级动作，我们需要把 total 从对应列挪到 none 列
            if original_action != SyncAction.NONE:
                orig_key = original_action.value.lower()
                if orig_key in summary and summary[orig_key]["total"] > 0:
                    summary[orig_key]["total"] -= 1
                    summary["none"]["total"] += 1
            return

        # 正常记录成功/失败/跳过
        key = action.value.lower()
        if result_status == _TaskStatus.SUCCESS:
            summary[key]["success"] += 1
            summary["total"]["success"] += 1
        elif result_status == _TaskStatus.FAILED:
            summary[key]["failed"] += 1
            summary["total"]["failed"] += 1
        elif result_status == _TaskStatus.SKIPPED:
            summary[key]["skipped"] += 1
            summary["total"]["skipped"] += 1
    
    def set_collection(self, collection: "DataCollection") -> None:
        """
        设置 Collection（在 load_all/sync_all 前调用）
        
        Args:
            collection: 数据集合
        """
        self._collection = collection
        collection.set_state_machine_runtime(self._ensure_sm_runtime())
        # 同时设置到所有已注册的 handler
        for handler in self._handlers.values():
            handler.set_collection(collection)
    
    # ========== Handler 管理 ==========
    
    def add_handler(self, handler: "NodeHandler") -> None:
        """
        添加 Handler 到当前 datasource
        
        Args:
            handler: 节点处理器实例
            
        示例：
            datasource.add_handler(ContractNodeHandler(api_client))
        """
        self._handlers[handler.node_type] = handler
    
    def get_handler(self, node_type: str) -> "NodeHandler":
        """获取 Handler"""
        if node_type not in self._handlers:
            raise KeyError(f"Handler for '{node_type}' not registered")
        return self._handlers[node_type]

    async def _upsert_loaded_nodes(
        self,
        handler: "NodeHandler",
        node_type: str,
        nodes: List["SyncNode"]
    ) -> None:
        """将加载的节点写入 Collection（按 data_id 优先命中已有节点）"""
        if self._collection is None:
            raise RuntimeError("Collection not set. Call set_collection() first.")

        for node in nodes:
            existing_node = None
            if node.data_id:
                existing_node = self._collection.get_by_data_id(node_type, node.data_id)
            load_source = f"{self.__class__.__name__}.load"

            if existing_node is not None:
                data_payload = node.get_data()
                existing_node.depend_ids = list(node.depend_ids or [])
                if node.context:
                    existing_node.context.update(node.context)
                existing_node.set_data(data_payload)
                existing_node.set_origin_data(data_payload)
                if existing_node.context.pop("_loaded_from_persistence", False):
                    existing_node.append_log(f"加载来源: persistence -> {load_source} 覆盖刷新")
                else:
                    existing_node.append_log(f"加载来源: {load_source} 覆盖刷新")

                await self._collection.update(existing_node)
                self._on_node_loaded(handler, existing_node)
            else:
                node.append_log(f"加载来源: {load_source} 新增节点")
                await self._collection.add(node)
                self._on_node_loaded(handler, node)
    
    # ========== 数据加载 ==========
    
    async def load_all(
        self,
        order: Optional[List[str]] = None,
        data_type: Optional[str] = None
    ) -> None:
        """
        按依赖顺序加载所有数据
        
        注意：调用前需先 set_collection()
        
        Args:
            order: 节点类型顺序（如 ["project", "contract", "construction"])
            data_type: 仅加载单一类型（与 order 互斥）
            
        工作流程：
        1. 按顺序遍历每个节点类型
        2. 调用 Handler.load(collection)
        3. Handler 可以从 collection 查询依赖节点（如 Contract 查询 Project）
        4. 使用 Handler.create_node() 创建 SyncNode
        5. 添加到 Collection
        
        示例：
            datasource.set_collection(collection)
            await datasource.load_all(order=["project", "contract"])
        """
        if self._collection is None:
            raise RuntimeError("Collection not set. Call set_collection() first.")
        
        if data_type is not None:
            order_to_load = [data_type]
        elif order is not None:
            order_to_load = order
        else:
            order_to_load = list(self._handlers.keys())

        for node_type in order_to_load:
            handler = self.get_handler(node_type)

            # 初始化统计
            self._init_node_stats(node_type)

            try:
                # Handler 加载数据并创建节点
                nodes = await handler.load()

                print(f"Loaded nodes for {node_type}: {len(nodes)}")

                # 将节点写入 Collection：优先按 data_id 命中已有节点并更新
                await self._upsert_loaded_nodes(handler, node_type, nodes)

                # 统计加载的节点数
                self._stats[node_type]["loaded"] += len(nodes)
            except Exception as exc:
                print(f"❌ [{node_type}] 加载失败: {exc}")
                continue
    
    # ========== 同步执行 ==========
    
    async def sync_all(
        self,
        order: Optional[List[str]] = None,
        data_type: Optional[str] = None,
        poll_async_tasks: bool = True
    ) -> Dict[str, Dict[str, str]]:
        """
        按依赖顺序执行同步
        
        注意：调用前需先 set_collection()
        
        Args:
            order: 节点类型顺序
            data_type: 仅同步单一类型（与 order 互斥）
            
        工作流程：
        1. 按顺序遍历每个节点类型
        2. 筛选需要同步的节点（status=PENDING, action!=NONE）
        3. 调用 Handler 的 create/update/delete
        4. 根据 TaskResult 更新节点状态
        5. 处理异步任务轮询
        
        状态流转：
        - PENDING + action=CREATE/UPDATE/DELETE → IN_PROGRESS → SUCCESS/FAILED
        - PENDING + action=NONE → 保持 PENDING
        - DEPENDENCY_ERROR / ABNORMAL / WARNING → SKIPPED
        
        示例：
            datasource.set_collection(collection)
            await datasource.sync_all(order=["project", "contract"])
        """
        if self._collection is None:
            raise RuntimeError("Collection not set. Call set_collection() first.")
        
        if data_type is not None:
            order_to_sync = [data_type]
        elif order is not None:
            order_to_sync = order
        else:
            order_to_sync = list(self._handlers.keys())

        async_tasks_by_type: Dict[str, Dict[str, str]] = {}

        for node_type in order_to_sync:
            handler = self.get_handler(node_type)
            
            # 批量同步该类型的所有节点
            async_tasks = await self._sync_nodes(handler, poll_async_tasks=poll_async_tasks)
            if async_tasks:
                async_tasks_by_type[node_type] = async_tasks

        return async_tasks_by_type
    
    async def _sync_nodes(
        self,
        handler: "NodeHandler",
        poll_async_tasks: bool = True
    ) -> Dict[str, str]:
        """
        同步一批节点（批量接口）
        
        Args:
            handler: 节点处理器
            
        流程：
        1. DataSource 筛选需要处理的节点
        2. 调用 handler.create_all(nodes)
        3. 调用 handler.update_all(nodes)
        4. 调用 handler.delete_all(nodes)
        5. 根据 List[TaskResult] 更新节点状态
        6. 收集并轮询异步任务
        """
        from ..common.types import SyncAction, SyncStatus
        from .task_result import TaskStatus as _TaskStatus
        
        if self._collection is None:
            raise RuntimeError("Collection not set. Call set_collection() first.")
        collection = self._collection
        
        # 初始化统计
        self._init_node_stats(handler.node_type)
        
        async_tasks: Dict[str, str] = {}  # {node_id: task_id}
        
        # DataSource 负责筛选需要创建的节点（排除已失败的节点）
        create_nodes = [
            n
            for n in collection.filter_by_state_ids(
                node_type=handler.node_type,
                state_ids=["S06"],
            )
            if n.action == SyncAction.CREATE
        ]
        
        # DataSource 负责筛选需要更新的节点（排除已失败的节点）
        update_nodes = [
            n
            for n in collection.filter_by_state_ids(
                node_type=handler.node_type,
                state_ids=["S07"],
            )
            if n.action == SyncAction.UPDATE
        ]
        
        # DataSource 负责筛选需要删除的节点（排除已失败的节点）
        delete_nodes = [
            n
            for n in collection.filter_by_state_ids(
                node_type=handler.node_type,
                state_ids=["S09"],
            )
            if n.action == SyncAction.DELETE
        ]

        if delete_nodes:
            raise NotImplementedError(
                f"[{handler.node_type}] delete path is not implemented yet, "
                f"but found {len(delete_nodes)} node(s) in S09/DELETE."
            )

        # 记录本次 sync 的动作统计（在 action 被重置前）
        for node in create_nodes:
            self._action_by_node_id[node.node_id] = SyncAction.CREATE
        for node in update_nodes:
            self._action_by_node_id[node.node_id] = SyncAction.UPDATE
        for node in delete_nodes:
            self._action_by_node_id[node.node_id] = SyncAction.DELETE

        none_count = len(
            self._collection.filter(
                node_type=handler.node_type,
                node_filter={"action": SyncAction.NONE}
            )
        )
        self._set_action_totals(
            handler.node_type,
            create=len(create_nodes),
            update=len(update_nodes),
            delete=len(delete_nodes),
            none=none_count,
        )

        # 批量创建（传入节点列表）
        if create_nodes:
            logger.info(f"[{handler.node_type}] create提交开始: nodes={len(create_nodes)}")
            try:
                create_results = await handler.create_all(create_nodes)
                create_success = 0
                create_in_progress = 0
                create_failed = 0
                create_skipped = 0
                for result in create_results:
                    if result.status == _TaskStatus.SUCCESS:
                        create_success += 1
                    elif result.status == _TaskStatus.IN_PROGRESS:
                        create_in_progress += 1
                    elif result.status == _TaskStatus.FAILED:
                        create_failed += 1
                    elif result.status == _TaskStatus.SKIPPED:
                        create_skipped += 1
                    await self._handle_task_result(handler, result, async_tasks)
                logger.info(
                    f"[{handler.node_type}] create提交完成: success={create_success}, "
                    f"in_progress={create_in_progress}, failed={create_failed}, skipped={create_skipped}"
                )
            except Exception as e:
                print(f"❌ [{handler.node_type}] create_all 异常: {e}")
                for node in create_nodes:
                    ok = self._apply_sync_execute_state(
                        node,
                        handler_status=_TaskStatus.FAILED,
                        detail=str(e),
                    )
                    if not ok:
                        raise RuntimeError(f"[{handler.node_type}] create_all exception cannot map to state machine")
        
        # 批量更新（传入节点列表）
        if update_nodes:
            try:
                update_results = await handler.update_all(update_nodes)
                for result in update_results:
                    await self._handle_task_result(handler, result, async_tasks)
            except Exception as e:
                print(f"❌ [{handler.node_type}] update_all 异常: {e}")
                for node in update_nodes:
                    ok = self._apply_sync_execute_state(
                        node,
                        handler_status=_TaskStatus.FAILED,
                        detail=str(e),
                    )
                    if not ok:
                        raise RuntimeError(f"[{handler.node_type}] update_all exception cannot map to state machine")
        
        # 批量删除（delete stage 当前未实现，检测到 S09 会在上方直接 fail-fast）
        
        # 轮询异步任务
        if async_tasks and poll_async_tasks:
            logger.info(
                f"[{handler.node_type}] create已提交，push_id检查即将开始: pending={len(async_tasks)}, "
                f"first_wait={self._poll_interval:.3f}s"
            )
            await self._poll_async_tasks(handler, async_tasks)
        
        # 统计同步结果
        all_nodes = self._collection.filter(node_type=handler.node_type)
        synced_count = len([n for n in all_nodes if n.action != SyncAction.NONE])
        success_count = len([n for n in all_nodes if n.status == SyncStatus.SUCCESS])
        failed_count = len([n for n in all_nodes if n.status == SyncStatus.FAILED])
        
        self._stats[handler.node_type]["synced"] = synced_count
        self._stats[handler.node_type]["success"] = success_count
        self._stats[handler.node_type]["failed"] = failed_count

        return async_tasks

    async def poll_async_tasks(self, async_tasks_by_type: Dict[str, Dict[str, str]]) -> None:
        """由 Pipeline 调度的异步任务轮询"""
        for node_type, async_tasks in async_tasks_by_type.items():
            if not async_tasks:
                continue
            handler = self.get_handler(node_type)
            await self._poll_async_tasks(handler, async_tasks)
    
    async def _handle_task_result(
        self,
        handler: "NodeHandler",
        result: "TaskResult",
        async_tasks: Dict[str, str]
    ) -> None:
        """
        处理 Handler 返回的 TaskResult
        
        Args:
            collection: Collection 上下文
            result: Handler 返回的结果（包含 node_id）
            async_tasks: 异步任务字典
        """
        from .task_result import TaskStatus as _TaskStatus
        
        if not result.node_id:
            # 没有 node_id，无法定位节点
            return
        
        if self._collection is None:
            # Collection 未设置，无法处理结果
            return
        
        # 从 self._collection 中查找节点
        node = self._collection.get(result.node_id)
        if not node:
            return

        if result.status == _TaskStatus.SUCCESS:
            if await self._intercept_success_result(handler, node, result, async_tasks):
                return
        
        if result.status == _TaskStatus.SUCCESS:
            await self._handle_success_result(node, result)
            return

        if result.status == _TaskStatus.FAILED:
            self._on_failed_result(handler, node, result)
            await self._handle_failed_result(node, result)
            return

        if result.status == _TaskStatus.SKIPPED:
            await self._handle_skipped_result(node, result)
            return

        if result.status == _TaskStatus.IN_PROGRESS:
            self._on_in_progress_result(handler, node, result)
            await self._handle_in_progress_result(node, result, async_tasks)
            return

    def _write_back_data_id_to_data(self, node, data_id: str) -> None:
        data = node.get_data()
        if data is None:
            return

        schema = node.get_schema()
        candidate_keys = ["id", "_id"]
        model_fields = get_pydantic_model_fields(schema)

        for key in candidate_keys:
            if key in data:
                data[key] = data_id
                node.set_data(data)
                return
            if key in model_fields:
                data[key] = data_id
                node.set_data(data)
                return

    async def _apply_success_result(self, node, data_id: Optional[str], previous_data_id: str) -> None:
        from ..common.types import SyncAction

        if data_id and node.action == SyncAction.CREATE:
            if node.data_id != data_id:
                raise RuntimeError(
                    f"[{node.node_type}] data_id must be updated in event apply path: expected={data_id}, actual={node.data_id}"
                )
            self._write_back_data_id_to_data(node, data_id)

            if self._collection is not None and data_id != previous_data_id:
                await self._collection.update(node)

        if node.action == SyncAction.UPDATE:
            current_data = node.get_data()
            if current_data is not None:
                node.set_origin_data(current_data)

        if node.action == SyncAction.DELETE:
            if self._collection is None:
                return
            await self._collection.delete(node.node_id)
            return

        # 成功后 action 是否重置由上层策略决定
    
    async def _poll_async_tasks(
        self,
        handler: "NodeHandler",
        async_tasks: Dict[str, str]
    ) -> None:
        """
        轮询异步任务直到全部完成
        
        Args:
            handler: 节点处理器
            collection: Collection 上下文
            async_tasks: {node_id: task_id} 映射
            
        流程：
        1. 批量调用 Handler.poll_tasks()
        2. 更新节点状态
        3. 如果还有未完成任务，等待后重试
        """
        from .task_result import TaskStatus as _TaskStatus
        
        max_retries = self._poll_max_retries
        retry_count = 0
        poll_interval = self._poll_interval  # 轮询间隔（秒）
        
        if self._collection is None:
            # Collection 未设置，无法轮询任务
            return

        if async_tasks and poll_interval > 0:
            logger.info(
                f"[{handler.node_type}] push_id首次检查前等待 {poll_interval:.3f}s, pending={len(async_tasks)}"
            )
            await asyncio.sleep(poll_interval)
        
        while async_tasks and retry_count < max_retries:
            logger.info(
                f"[{handler.node_type}] push_id检查 {retry_count + 1}/{max_retries}, pending={len(async_tasks)}"
            )
            # 批量查询
            task_ids = list(async_tasks.values())
            results = await handler.poll_tasks(task_ids)
            
            # 处理结果
            completed_nodes = []
            for node_id, task_id in list(async_tasks.items()):
                if task_id not in results:
                    continue
                
                result = results[task_id]
                node = self._collection.get(node_id)
                if not node:
                    completed_nodes.append(node_id)
                    continue
                
                if result.status == _TaskStatus.SUCCESS:
                    self._on_poll_result(handler, node, task_id, result)
                    previous_data_id = node.data_id
                    ok = self._apply_sync_execute_state(
                        node,
                        handler_status=result.status,
                        detail=f"异步任务成功完成: {task_id}",
                        result_data_id=result.data_id,
                    )
                    if not ok:
                        raise RuntimeError(f"[{node.node_type}] async SUCCESS cannot map to state machine: node={node.node_id}")
                    self._record_action_result(node, result.status)
                    await self._apply_success_result(node, result.data_id, previous_data_id=previous_data_id)
                    node.error = None
                    logger.info(
                        f"[{handler.node_type}] polling成功: node={node.node_id}, push_id={task_id}, data_id={result.data_id or node.data_id}"
                    )
                    completed_nodes.append(node_id)
                    
                elif result.status == _TaskStatus.FAILED:
                    self._on_poll_result(handler, node, task_id, result)
                    ok = self._apply_sync_execute_state(
                        node,
                        handler_status=result.status,
                        detail=f"异步任务失败: {result.error}",
                    )
                    if not ok:
                        raise RuntimeError(f"[{node.node_type}] async FAILED cannot map to state machine: node={node.node_id}")
                    self._record_action_result(node, result.status)
                    node.error = result.error
                    completed_nodes.append(node_id)
                
                # IN_PROGRESS 继续等待
            
            # 移除已完成的任务
            for node_id in completed_nodes:
                async_tasks.pop(node_id, None)
            
            # 还有未完成任务，等待后重试
            if async_tasks:
                retry_count += 1
        
        # 超时未完成的任务标记为失败
        if async_tasks:
            for node_id in async_tasks:
                node = self._collection.get(node_id)
                if node:
                    timeout_msg = f"异步任务超时 (等待 {max_retries * poll_interval} 秒后仍未完成)"
                    ok = self._apply_sync_execute_state(
                        node,
                        handler_status=_TaskStatus.FAILED,
                        poll_timeout=True,
                        detail=timeout_msg,
                    )
                    if not ok:
                        raise RuntimeError(f"[{node.node_type}] poll timeout cannot map to state machine: node={node.node_id}")
                    node.error = f"Async task timeout after {max_retries * poll_interval} seconds"
                    self._record_action_result(node, _TaskStatus.FAILED)
    
    # ========== 统计信息 ==========
    
    def get_sync_summary(self, collection: "DataCollection") -> Dict[str, Any]:
        """
        获取同步统计信息
        
        Returns:
            {
                "total": 总节点数,
                "success": 成功数,
                "failed": 失败数,
                "in_progress": 进行中,
                "skipped": 跳过数,
                "by_type": {...}  # 按类型统计
            }
        """
        nodes = list(collection._nodes.values())
        
        summary = {
            "total": len(nodes),
            "success": sum(1 for n in nodes if n.status == SyncStatus.SUCCESS),
            "failed": sum(1 for n in nodes if n.status == SyncStatus.FAILED),
            "in_progress": sum(1 for n in nodes if n.status == SyncStatus.IN_PROGRESS),
            "skipped": sum(1 for n in nodes if n.status == SyncStatus.SKIPPED),
            "by_type": {}
        }
        
        # 按类型统计
        for node_type in self._handlers.keys():
            type_nodes = collection.filter(node_type=node_type)
            summary["by_type"][node_type] = {
                "total": len(type_nodes),
                "success": sum(1 for n in type_nodes if n.status == SyncStatus.SUCCESS),
                "failed": sum(1 for n in type_nodes if n.status == SyncStatus.FAILED),
            }
        
        return summary

    async def initialize(self) -> None:
        """初始化数据源资源（子类可覆盖）"""
        return None

    async def close(self) -> None:
        """关闭数据源资源（子类可覆盖以实现特定的清理逻辑）"""
        return None
