"""
ID 解析模块：处理本地 data_id 到远程 data_id 的转换
"""
from typing import TYPE_CHECKING, Optional, List, Dict, Any, cast
from ..common.collection import DataCollection
from ..common.binding import BindingManager

if TYPE_CHECKING:
    from ..common.sync_node import SyncNode


class IDResolver:
    """
    ID 解析器：将本地业务 ID 转换为远程业务 ID
    
    用于自动绑定场景，解决跨系统的 ID 引用问题。
    例如：本地 Contract 的 project_id="P1" 需要转换为远程的 project_id="RP_99"
    """
    
    def __init__(
        self,
        local_collection: DataCollection,
        remote_collection: DataCollection,
        binding_manager: BindingManager
    ):
        self.local_collection = local_collection
        self.remote_collection = remote_collection
        self.binding_manager = binding_manager
    
    async def resolve_id(
        self,
        source_data_id: str,
        node_type_hint: Optional[str] = None,
        to_remote: bool = True
    ) -> Optional[str]:
        """
        将源系统的 data_id 转换为目标系统的 data_id。
        
        Args:
            source_data_id: 源系统的业务 ID
            node_type_hint: 节点类型提示
            to_remote: True 表示从本地转换到远程，False 表示从远程转换到本地
        
        Returns:
            目标系统的业务 ID，如果转换失败则返回 None
        """
        if to_remote:
            # 1. 查找源节点（本地）
            src_node = self._find_node_by_data_id(
                self.local_collection, 
                source_data_id, 
                node_type_hint
            )
            if not src_node:
                return None
            
            # 2. 查找目标 node_id (远程)
            target_node_id = await self.binding_manager.get_remote_id(
                src_node.node_type,
                src_node.node_id
            )
            if not target_node_id:
                return None
            
            # 3. 查找远程节点并提取 data_id
            target_node = self.remote_collection.get(target_node_id)
            if not target_node:
                return None
            return target_node.data_id
        else:
            # 1. 查找源节点（远程）
            src_node = self._find_node_by_data_id(
                self.remote_collection, 
                source_data_id, 
                node_type_hint
            )
            if not src_node:
                return None
            
            # 2. 查找目标 node_id (本地)
            target_node_id = await self.binding_manager.get_local_id(
                src_node.node_type,
                src_node.node_id
            )
            if not target_node_id:
                return None
            
            # 3. 查找本地节点并提取 data_id
            target_node = self.local_collection.get(target_node_id)
            if not target_node:
                return None
            return target_node.data_id
    
    async def resolve_ids(
        self,
        local_data_ids: List[str],
        node_type_hints: Optional[List[Optional[str]]] = None
    ) -> List[Optional[str]]:
        """
        批量转换本地 data_id 列表为远程 data_id 列表。
        
        Args:
            local_data_ids: 本地业务 ID 列表
            node_type_hints: 节点类型提示列表（可选）
        
        Returns:
            远程业务 ID 列表，保持顺序，转换失败的位置为 None
        """
        if node_type_hints is None:
            node_type_hints = cast(List[Optional[str]], [None] * len(local_data_ids))
        
        results: List[Optional[str]] = []
        for local_id, hint in zip(local_data_ids, node_type_hints):
            remote_id = await self.resolve_id(local_id, hint)
            results.append(remote_id)
        
        return results
    
    def _find_node_by_data_id(
        self,
        collection: DataCollection,
        data_id: str,
        node_type_hint: Optional[str] = None
    ):
        """
        在 collection 中查找指定 data_id 的节点。
        
        如果提供了 node_type_hint，则只在该类型中查找。
        否则通过 collection 内部的 data_id 索引进行 O(1) 查找。
        
        Args:
            collection: 数据集合
            data_id: 业务数据 ID
            node_type_hint: 节点类型提示
        
        Returns:
            找到的节点，或 None
        """
        if node_type_hint:
            # 有类型提示，直接使用高效查找
            return collection.get_by_data_id(node_type_hint, data_id)

        return collection.get_by_data_id_any(data_id)
    
    async def resolve_business_key_ids(
        self,
        business_key_fields: List[str],
        local_data: Dict[str, Any],
        node_type_hints: Optional[Dict[str, str]] = None
    ) -> Dict[str, Optional[str]]:
        """
        解析业务键中的所有 ID 字段，转换为远程 ID。
        
        识别规则：
        - 字段名以 "_id" 结尾
        - 字段名为 "id"
        
        Args:
            business_key_fields: 业务键字段列表
            local_data: 本地数据对象
            node_type_hints: ID 字段到 node_type 的映射（可选）
        
        Returns:
            字段名到远程 ID 的映射，转换失败的为 None
        """
        if node_type_hints is None:
            node_type_hints = {}
        
        resolved = {}
        
        for field_name in business_key_fields:
            # 判断是否是 ID 字段
            is_id_field = field_name.endswith("_id") or field_name == "id"
            
            if not is_id_field:
                # 非 ID 字段，不需要转换
                resolved[field_name] = local_data.get(field_name)
                continue
            
            # ID 字段，需要转换
            local_id = local_data.get(field_name)
            
            if local_id is None:
                resolved[field_name] = None
                continue
            
            # 转换为远程 ID
            hint = node_type_hints.get(field_name)
            remote_id = await self.resolve_id(local_id, hint)
            resolved[field_name] = remote_id
        
        return resolved
    
    async def replace_ids_in_data(
        self,
        node: "SyncNode",
        depend_field_names: set[str],
        node_type: str,
        id_field_hints: Dict[str, str],
        to_remote: bool = True
    ) -> None:
        """
        替换节点data中的所有ID字段（源系统ID -> 目标系统ID）。
        
        Args:
            node: 待处理的节点
            depend_field_names: 依赖字段名集合
            node_type: 节点类型（用于日志）
            id_field_hints: ID字段到node_type的提示映射
            to_remote: 转换方向
        """
        import logging
        logger = logging.getLogger(__name__)
        
        data_dict = node.get_data()
        if not data_dict:
            return
        
        # 遍历所有字段
        for field_name, value in list(data_dict.items()):
            # 判断是否是ID字段
            is_id_field = field_name.endswith("_id") or field_name.endswith("_ids") or field_name == "id"
            is_primary_id_field = field_name in ("id", "_id")
            
            if not is_id_field:
                continue
            
            if value is None:
                continue
            if value == "":
                continue
            
            # 判断是否是依赖字段
            is_depend_field = field_name in depend_field_names
            
            # 获取node_type提示
            hint = id_field_hints.get(field_name)
            
            # 如果是主 ID 字段且没有提示，则使用当前节点类型作为提示
            if is_primary_id_field and not hint:
                hint = node_type
            
            # 解析ID
            target_value = await self._resolve_id_field_value(
                value=value,
                hint=hint,
                node=node,
                node_type=node_type,
                field_name=field_name,
                is_depend_field=is_depend_field,
                is_primary_id_field=is_primary_id_field,
                to_remote=to_remote,
                logger=logger,
            )
            data_dict[field_name] = target_value
        
        # 将修改后的数据写回节点
        node.set_data(data_dict)

    async def resolve_and_validate(
        self,
        data: Dict[str, Any],
        node_type: str,
        depend_fields: Dict[str, str],
        to_remote: bool,
    ) -> tuple[Optional[Dict[str, Any]], Optional[str]]:
        """
        ID 替换 + 严格验证（一站式方法）
        
        用途：CREATE/UPDATE 时将源端的依赖 ID 转换为目标端 ID，并验证转换结果
        
        流程：
        1. 无依赖字段 → 直接返回原数据
        2. 创建临时节点 → 调用 replace_ids_in_data 执行 ID 替换
        3. 严格验证：检查所有依赖字段是否成功转换
        
        验证逻辑（与 bind 阶段依赖检查的区别）：
        - bind 阶段：检查同端依赖（本地 material 的 contract_id 在本地是否存在）
        - create 阶段：检查能否转换为目标端 ID（本地 contract_id 能否找到远程 ID）
        
        触发场景示例：
          本地 material: {"contract_id": "local_c123"}
          bind 检查：✅ 本地 contract(data_id=local_c123) 存在
          create 转换：❌ 远程 contract 还没创建，找不到远程 ID
          结果：虽然本地依赖满足，但无法推送到远程
        
        Args:
            data: 源数据字典
            node_type: 节点类型
            depend_fields: 依赖字段映射 {field_name: dep_node_type}
            to_remote: True=本地→远程, False=远程→本地
        
        Returns:
            (resolved_data, None): 成功
            (None, error_msg): 失败
        """
        from ..common.registry import DomainRegistry
        
        # 无依赖字段，直接返回原数据
        if not depend_fields:
            return data, None
        
        depend_field_names = set(depend_fields.keys())
        # id_field_hints 与 depend_fields 相同（field_name -> node_type）
        id_field_hints = dict(depend_fields)
        
        # 创建临时节点用于 ID 替换
        temp_node_class = DomainRegistry.get_node_class(node_type)
        temp_node = temp_node_class(
            node_id="temp",
            data_id="",
            data=data
        )
        
        # 执行 ID 替换
        await self.replace_ids_in_data(
            temp_node,
            depend_field_names,
            node_type,
            id_field_hints,
            to_remote=to_remote
        )
        
        resolved_data = temp_node.get_data()
        if not resolved_data:
            return None, "ID 解析失败（数据为空）"
        
        # 严格验证：检查所有依赖字段是否成功转换
        failed_fields = []
        for field_name, dep_type in depend_fields.items():
            original_value = data.get(field_name)
            resolved_value = resolved_data.get(field_name)
            
            # 原本有值（源端 ID），但解析后为空（找不到目标端 ID）
            # 说明依赖节点虽然在源端存在，但在目标端还没就绪
            if self._has_unresolved_dependency_value(original_value, resolved_value):
                failed_fields.append(f"{field_name}({dep_type})={original_value}")
        
        if failed_fields:
            direction = "远程" if to_remote else "本地"
            error_msg = f"依赖字段无法解析到{direction} ID: {', '.join(failed_fields)}"
            return None, error_msg
        
        return resolved_data, None

    @staticmethod
    def _has_unresolved_dependency_value(original_value: Any, resolved_value: Any) -> bool:
        if isinstance(original_value, list) and isinstance(resolved_value, list):
            if len(original_value) != len(resolved_value):
                return True
            for original_item, resolved_item in zip(original_value, resolved_value):
                if original_item in (None, ""):
                    continue
                if not resolved_item:
                    return True
            return False

        if isinstance(original_value, list):
            return any(item not in (None, "") for item in original_value) and not resolved_value

        return bool(original_value) and not resolved_value

    async def _resolve_id_field_value(
        self,
        *,
        value: Any,
        hint: Optional[str],
        node: "SyncNode",
        node_type: str,
        field_name: str,
        is_depend_field: bool,
        is_primary_id_field: bool,
        to_remote: bool,
        logger,
    ) -> Any:
        if isinstance(value, list):
            resolved_items = []
            has_failure = False
            for item in value:
                if item is None or item == "":
                    resolved_items.append("")
                    continue

                target_id = await self.resolve_id(str(item), hint, to_remote=to_remote)
                if target_id is None:
                    has_failure = True
                    resolved_items.append("")
                else:
                    resolved_items.append(target_id)

            if has_failure:
                self._log_id_resolution_failure(
                    logger=logger,
                    node=node,
                    node_type=node_type,
                    field_name=field_name,
                    value=value,
                    is_depend_field=is_depend_field,
                    is_primary_id_field=is_primary_id_field,
                    to_remote=to_remote,
                )
            return resolved_items

        target_id = await self.resolve_id(str(value), hint, to_remote=to_remote)
        if target_id is None:
            self._log_id_resolution_failure(
                logger=logger,
                node=node,
                node_type=node_type,
                field_name=field_name,
                value=value,
                is_depend_field=is_depend_field,
                is_primary_id_field=is_primary_id_field,
                to_remote=to_remote,
            )
            return ""

        return target_id

    @staticmethod
    def _log_id_resolution_failure(
        *,
        logger,
        node: "SyncNode",
        node_type: str,
        field_name: str,
        value: Any,
        is_depend_field: bool,
        is_primary_id_field: bool,
        to_remote: bool,
    ) -> None:
        collection_name = "local" if to_remote else "remote"
        node_data_id = node.data_id or "N/A"
        direction = "remote" if to_remote else "local"

        if is_depend_field:
            logger.warning(
                f"[{node_type}] {collection_name.capitalize()} node {node.node_id} "
                f"(data_id={node_data_id}): Dependency field '{field_name}' "
                f"with value '{value}' failed to resolve to {direction} ID"
            )
        elif not is_primary_id_field:
            logger.warning(
                f"[{node_type}] {collection_name.capitalize()} node {node.node_id} "
                f"(data_id={node_data_id}): Non-dependency ID field '{field_name}' "
                f"with value '{value}' failed to resolve to {direction} ID, setting to empty"
            )
