from typing import TypeVar, Generic, Type, Dict, List, Optional, Any
from pydantic import BaseModel
import logging
from pathlib import Path

from ..common.collection import DataCollection
from ..common.binding import BindingManager
from ..common.sync_node import SyncNode
from .config import StrategyConfig, OrphanAction, UpdateDirection, ConfigPresets
from .strategy_ops import (
    get_phase1_reset_defaults,
    run_bind,
    run_create,
    run_delete,
    run_phase2_cleanup,
    run_update,
)
from ..engine import (
    StateMachineConfig,
    StateMachineRuntime,
)
from ..validation import SchemaDiffValidator
from .strategy_ops.compare_ops import normalized_data_for_compare

T = TypeVar("T", bound=BaseModel)
logger = logging.getLogger(__name__)

class BaseSyncStrategy(Generic[T]):
    """
    同步策略基类。
    纯逻辑层，不负责 IO 执行。
    
    子类可以通过覆盖 default_config 来设置默认配置：
    ```python
    class ProjectStrategy(DefaultSyncStrategy):
        default_config = StrategyConfig(
            auto_bind_fields=["name", "code"],
            local_orphan_action=OrphanAction.CREATE_REMOTE
        )
        schema = ProjectResponse
    ```
    
    跳过同步控制：
        skip_sync: bool - 如果为 True，则跳过该类型的 bind/create/update/sync 阶段
                          数据仍会被加载，但不会执行任何同步操作
                          适用于只需要加载数据供其他类型引用的场景（如 supplier）
    """
    # 类变量：默认配置（子类可覆盖）
    default_config: StrategyConfig = StrategyConfig()
    # 类变量：schema 类型（子类必须设置）
    schema: Type[T] = None  # type: ignore
    # 类变量：是否默认跳过同步（子类可覆盖）
    default_skip_sync: bool = False
    
    def __init__(
        self, 
        node_type: str, 
        local_collection: DataCollection, 
        remote_collection: DataCollection, 
        binding_manager: BindingManager,
    ):
        if self.schema is None:
            raise ValueError(f"{self.__class__.__name__} must define schema as a class variable")
        
        self.node_type = node_type
        self.local_collection = local_collection
        self.remote_collection = remote_collection
        self.binding_manager = binding_manager
        # 复制类级别的默认配置到实例
        self.config = self.default_config.model_copy(deep=True)
        # 是否跳过同步（可运行时修改）
        self.skip_sync = self.default_skip_sync
        # 统一由 Pipeline 注入 runtime；未注入时在执行阶段显式报错
        self.sm_runtime: Optional[StateMachineRuntime] = None
        self._schema_diff_validator: Optional[SchemaDiffValidator] = None
        
        # 配置逻辑校验
        self._validate_config()

    def ensure_runtime(self) -> StateMachineRuntime:
        runtime = self.sm_runtime
        if runtime is None:
            cfg_path = Path(__file__).resolve().parents[1] / "config" / "node_state_machine.yaml"
            runtime = StateMachineRuntime(StateMachineConfig.load(cfg_path))
            self.sm_runtime = runtime
        self.local_collection.set_state_machine_runtime(runtime)
        self.remote_collection.set_state_machine_runtime(runtime)
        return runtime

    def _validate_config(self) -> None:
        """
        校验配置的逻辑一致性。
        
        规则：
        - PUSH 模式：local_orphan_action 应为 CREATE_REMOTE，remote_orphan_action 应为 NONE
        - PULL 模式：local_orphan_action 应为 NONE，remote_orphan_action 应为 CREATE_LOCAL
        - NONE 模式：两者都应为 NONE
        """
        direction = self.config.update_direction
        local_action = self.config.local_orphan_action
        remote_action = self.config.remote_orphan_action
        
        # PUSH 模式检查
        if direction == UpdateDirection.PUSH:
            if local_action == OrphanAction.CREATE_LOCAL:
                logger.warning(
                    f"[{self.node_type}] Config inconsistency: "
                    f"update_direction=PUSH but local_orphan_action=CREATE_LOCAL (本地创建)。"
                    f"应使用 CREATE_REMOTE (推送到远程)"
                )
            if remote_action == OrphanAction.CREATE_LOCAL:
                logger.warning(
                    f"[{self.node_type}] Config inconsistency: "
                    f"update_direction=PUSH but remote_orphan_action=CREATE_LOCAL (本地创建)。"
                    f"PUSH 模式应设为 NONE 或 DELETE_REMOTE"
                )
        
        # PULL 模式检查
        elif direction == UpdateDirection.PULL:
            if local_action == OrphanAction.CREATE_REMOTE:
                logger.warning(
                    f"[{self.node_type}] Config inconsistency: "
                    f"update_direction=PULL but local_orphan_action=CREATE_REMOTE (推送到远程)。"
                    f"PULL 模式应设为 NONE 或 DELETE_LOCAL"
                )
            if remote_action in {OrphanAction.CREATE_REMOTE, OrphanAction.DELETE_LOCAL}:
                logger.warning(
                    f"[{self.node_type}] Config inconsistency: "
                    f"update_direction=PULL but remote_orphan_action={remote_action}。"
                    f"PULL 模式建议使用 CREATE_LOCAL 或 NONE"
                )
        
        # NONE 模式检查
        elif direction == UpdateDirection.NONE:
            if local_action != OrphanAction.NONE:
                logger.warning(
                    f"[{self.node_type}] Config inconsistency: "
                    f"update_direction=NONE but local_orphan_action={local_action}。"
                    f"应设为 NONE"
                )
            if remote_action != OrphanAction.NONE:
                logger.warning(
                    f"[{self.node_type}] Config inconsistency: "
                    f"update_direction=NONE but remote_orphan_action={remote_action}。"
                    f"应设为 NONE"
                )

    def set_config(self, config: StrategyConfig):
        """完全替换配置对象"""
        self.config = config
        self._schema_diff_validator = None
        self._validate_config()
    
    def update_config(self, **kwargs):
        """
        临时修改部分配置项。
        
        Example:
            strategy.update_config(
                local_orphan_action=OrphanAction.NONE,
                update_direction=UpdateDirection.PULL
            )
        """
        valid_fields = type(self.config).model_fields
        for key, value in kwargs.items():
            if key in valid_fields:
                setattr(self.config, key, value)
            else:
                logger.warning(f"[{self.node_type}] Unknown config key: {key}")
        self._schema_diff_validator = None
        # 更新配置后也要校验
        self._validate_config()
    
    def apply_preset(self, preset_name: str):
        """
        应用配置预设。
        
        Args:
            preset_name: 预设名称，可选：first_sync, daily_sync, repair_sync, pull_only, push_only
            
        Example:
            strategy.apply_preset("first_sync")
        """
        preset_config = ConfigPresets.get_preset(preset_name)
        self.config = preset_config
        self._schema_diff_validator = None
        self._validate_config()
        logger.info(f"[{self.node_type}] Applied preset: {preset_name}")

    def preprocess_compare_payload(self, payload: Dict[str, Any]) -> Dict[str, Any]:
        return payload

    def normalize_compare_payload(
        self,
        data: Optional[Dict[str, Any]],
        data_id_map: Optional[Dict[str, str]] = None,
        *,
        ignore_fields: Optional[set[str]] = None,
    ) -> Dict[str, Any]:
        prepared = self.preprocess_compare_payload(dict(data or {}))
        return normalized_data_for_compare(
            prepared,
            data_id_map,
            ignore_fields=ignore_fields,
        )

    def get_schema_diff_validator(self) -> SchemaDiffValidator:
        if self._schema_diff_validator is None:
            self._schema_diff_validator = SchemaDiffValidator(
                schema_name=self.schema.__name__,
                ignore_fields=self.config.compare_ignore_fields,
            )
        return self._schema_diff_validator

    def reset_schema_diff_validator(self) -> None:
        self.get_schema_diff_validator().reset()

    def emit_schema_diff_report(self) -> None:
        validator = self.get_schema_diff_validator()
        if not validator.has_records():
            return
        for line in validator.format_summary_lines():
            logger.info(f"[{self.node_type}] validate {line}")

    async def bind(self, **kwargs):
        """进行绑定逻辑判定，设置 node.binding_status 和 node.action"""
        raise NotImplementedError

    async def create(self) -> List[SyncNode]:
        """准备 CREATE 操作"""
        raise NotImplementedError

    async def update(self) -> List[SyncNode]:
        """准备 UPDATE 操作"""
        raise NotImplementedError

    def _needs_update(
        self,
        source_node: SyncNode,
        target_node: SyncNode,
        resolved_data: Optional[Dict[str, Any]] = None,
        data_id_map: Optional[Dict[str, str]] = None,
    ) -> bool:
        from .strategy_ops.update_ops import default_needs_update

        return default_needs_update(self, source_node, target_node, resolved_data, data_id_map)

    @classmethod
    def get_phase1_reset_defaults(cls) -> Dict[str, Any]:
        return get_phase1_reset_defaults()

    @classmethod
    async def run_reset(
        cls,
        *,
        node_types: List[str],
        local_collection: DataCollection,
        remote_collection: DataCollection,
        binding_manager: BindingManager,
        runtime: StateMachineRuntime,
    ) -> int:
        """
        执行加载后的重置清理（僵尸 CREATE 节点清理）。

        说明：
        - `DataCollection.load_from_persistence()` 仅恢复持久化状态；
        - 这里触发 E01：create_zombie -> S15（删除），其余 -> S00。
        """
        return await run_phase2_cleanup(
            node_types=node_types,
            local_collection=local_collection,
            remote_collection=remote_collection,
            binding_manager=binding_manager,
            runtime=runtime,
        )

    @classmethod
    async def run_phase2_reset(
        cls,
        *,
        node_types: List[str],
        local_collection: DataCollection,
        remote_collection: DataCollection,
        binding_manager: BindingManager,
        runtime: StateMachineRuntime,
    ) -> int:
        """兼容旧命名，等价于 `run_reset`。"""
        return await cls.run_reset(
            node_types=node_types,
            local_collection=local_collection,
            remote_collection=remote_collection,
            binding_manager=binding_manager,
            runtime=runtime,
        )


class DefaultSyncStrategy(BaseSyncStrategy[T]):
    """
    基于 STATE_DEFINITIONS.md 实现的标准化同步策略。
    采用四阶段管线：核心状态 -> 依赖检查 -> 自动匹配 -> 动作映射。
    """

    async def bind(self, **kwargs):
        self.ensure_runtime()
        await run_bind(self, **kwargs)



    def _get_id_field_hints(self) -> Dict[str, str]:
        """
        获取 ID 字段的 node_type 提示。
        
        默认实现：从 depend_fields 中提取。
        子类可以重写此方法提供更精确的映射。
        
        Returns:
            字段名到 node_type 的映射，例如 {"project_id": "project"}
        """
        return dict(self.config.depend_fields) if self.config.depend_fields else {}
    async def create(self) -> List[SyncNode]:
        self.ensure_runtime()
        return await run_create(self)

    async def update(self) -> List[SyncNode]:
        self.ensure_runtime()
        return await run_update(self)

    async def delete(self) -> List[SyncNode]:
        self.ensure_runtime()
        return await run_delete(self)


