"""
SyncNode - 核心状态机容器

将data存储为Dict，但支持schema验证和Pydantic模型转换。
提供统一的数据访问接口：get_data() / put_data()
"""

import copy
from contextlib import contextmanager
from typing import TypeVar, Generic, Optional, Any, Dict, List, Type
from pydantic import BaseModel, ValidationError

from .types import SyncAction, SyncStatus, BindingStatus

T = TypeVar("T", bound=BaseModel)


class SyncNode(Generic[T]):
    """
    核心状态机容器
    
    数据存储策略：
    - origin_data/data 内部存储为 Dict
    - 通过 get_data() 获取数据副本（深拷贝）
    - 通过 set_data() 设置数据（自动验证+深拷贝）
    - 通过 to_model() 转换为 Pydantic 模型实例
    
    基类 SyncNode 不应直接实例化，应通过子类使用。
    子类应设置类变量 node_type 和 schema：
    ```python
    class ContractSyncNode(SyncNode[ContractResponse]):
        node_type = "contract"
        schema = ContractResponse
    ```
    """
    
    # 类变量：子类必须设置
    node_type: str = None  # type: ignore
    schema: Type[T] = None  # type: ignore
    _CORE_FIELDS = {"binding_status", "action", "status", "data_id"}

    def __setattr__(self, name: str, value: Any) -> None:
        if (
            name in self._CORE_FIELDS
            and self.__dict__.get("_core_guard_enabled", False)
            and self.__dict__.get("_core_write_depth", 0) <= 0
        ):
            raise RuntimeError(
                f"Core field '{name}' can only be modified via event apply path for node={self.__dict__.get('node_id', '<uninitialized>')}"
            )
        object.__setattr__(self, name, value)

    @contextmanager
    def allow_core_state_write(self, source: str = "unknown"):
        depth = self.__dict__.get("_core_write_depth", 0)
        prev_source = self.__dict__.get("_core_write_source", "")
        object.__setattr__(self, "_core_write_depth", depth + 1)
        object.__setattr__(self, "_core_write_source", source)
        try:
            yield
        finally:
            object.__setattr__(self, "_core_write_depth", depth)
            object.__setattr__(self, "_core_write_source", prev_source)
    
    def __init__(
        self,
        node_id: str,
        data_id: str = "",
        depend_ids: Optional[List[str]] = None,
        data: Optional[Dict[str, Any]] = None,
        origin_data: Optional[Dict[str, Any]] = None,
        action: SyncAction = SyncAction.NONE,
        status: SyncStatus = SyncStatus.PENDING,
        binding_status: BindingStatus = BindingStatus.UNCHECKED,
        error: Optional[str] = None,
        context: Optional[Dict[str, Any]] = None,
        sync_log: Optional[str] = None
    ):
        object.__setattr__(self, "_core_guard_enabled", False)
        object.__setattr__(self, "_core_write_depth", 0)

        # 检查类变量是否设置
        if self.node_type is None:
            raise ValueError(
                f"{self.__class__.__name__} must define class variable 'node_type'. "
                f"Example: node_type = 'contract'"
            )
        if self.schema is None:
            raise ValueError(
                f"{self.__class__.__name__} must define class variable 'schema'. "
                f"Example: schema = YourSchema"
            )
        
        self.node_id = node_id
        self.data_id = data_id
        self.depend_ids = depend_ids if depend_ids is not None else []
        
        if origin_data is not None:
            self.origin_data = copy.deepcopy(origin_data)
        else:
            self.origin_data = None
        self.action = action
        self.status = status
        self.binding_status = binding_status
        self.error = error
        self.sync_log = sync_log  # Debug/info级别的日志信息
        
        # Context 字典：存储额外的上下文信息（如 project_id, contract_id 等）
        self.context: Dict[str, Any] = context if context is not None else {}
        
        # 在初始化时验证并设置 data
        if data is not None:
            self._validate_and_set_data(data)
            if self.origin_data is None:
                self.origin_data = copy.deepcopy(self.data)
        else:
            self.data = None

        object.__setattr__(self, "_core_guard_enabled", True)
    
    def _validate_and_set_data(self, data: Dict[str, Any]) -> None:
        """验证并设置数据（内部方法）"""
        if not isinstance(data, dict):
            self.data = None
            raise ValueError(f"Data validation failed for {self.node_type}: data must be a dict")

        try:
            # 基础兼容性：将 _id 转换为 id
            data_to_validate = data.copy()
            if "id" not in data_to_validate and "_id" in data_to_validate:
                data_to_validate["id"] = data_to_validate["_id"]

            model = self.schema.model_validate(data_to_validate)
            # 使用 model_dump() 获得完整校验后的数据，去掉 schema 之外的字段
            self.data = model.model_dump()
        except Exception as e:
            # 如果出错就存 None, 然后 error 里加个错
            self.data = None
            self.error = f"Validation failed: {str(e)}"
            # 不要轻易降级错误。如果校验失败，应抛出 ValueError 供上层处理
            raise ValueError(f"Data validation failed for {self.node_type}: {e}")
    
    # ===== 数据访问方法 =====
    
    def get_data(self, exclude_unset: bool = False) -> Optional[Dict[str, Any]]:
        """
        获取数据的副本。
        
        Args:
            exclude_unset: 是否排除未设置（默认值）的字段。
            
        Returns:
            数据字典的副本，如果为None则返回None
        """
        if self.data is None:
            return None
        
        if not exclude_unset:
            return copy.deepcopy(self.data)
            
        try:
            model = self.schema.model_validate(self.data)
            return model.model_dump(exclude_unset=True)
        except Exception:
            return copy.deepcopy(self.data)
    
    def set_data(self, data: Optional[Dict[str, Any]], validate: bool = True) -> None:
        """
        设置数据（自动深拷贝）。
        
        Args:
            data: 要设置的数据字典
            validate: 是否进行schema验证（默认True）
        
        Raises:
            ValueError: 如果数据验证失败
        """
        if data is None:
            self.data = None
            return
        
        if validate:
            self._validate_and_set_data(data)
        else:
            self.data = copy.deepcopy(data)
    
    def get_origin_data(self) -> Optional[Dict[str, Any]]:
        """
        获取原始数据的深拷贝副本。
        
        Returns:
            原始数据字典的副本，如果为None则返回None
        """
        if self.origin_data is None:
            return None
        return copy.deepcopy(self.origin_data)
    
    def set_origin_data(self, data: Optional[Dict[str, Any]]) -> None:
        """
        设置原始数据（自动深拷贝）。
        
        Args:
            data: 要设置的原始数据字典
        """
        if data is None:
            self.origin_data = None
            return

        if self.schema is None:
            raise ValueError(f"Cannot set origin_data without schema for {self.node_type}")

        try:
            model = self.schema.model_validate(copy.deepcopy(data))
            # 使用 exclude_unset=True 确保 origin_data 也只记录实际提供/返回的字段
            self.origin_data = model.model_dump(exclude_unset=True)
        except AttributeError as e:
            raise ValueError(
                f"Origin data validation failed for {self.node_type}: model_dump not available"
            ) from e
        except (ValidationError, TypeError, ValueError) as e:
            raise ValueError(f"Origin data validation failed for {self.node_type}: {e}") from e
    
    # ===== Schema 相关方法 =====
    
    def get_schema(self) -> Optional[Type[T]]:
        """
        获取数据的 Schema 类型。
        
        Returns:
            Pydantic Model 类型，如果未设置则返回 None
        """
        return self.schema
    
    def set_schema(self, schema: Type[T]) -> None:
        """
        设置数据的 Schema 类型。
        
        Args:
            schema: Pydantic Model 类型
        """
        self.schema = schema
    
    def validate(self) -> bool:
        """
        验证当前数据是否符合 schema。
        
        Returns:
            True 如果验证通过或无 schema，False 如果验证失败
        """
        if self.schema is None or self.data is None:
            return True
        
        try:
            self.schema.model_validate(self.data)
            return True
        except ValidationError:
            return False
    
    def to_model(self) -> Optional[T]:
        """
        将数据转换为 Pydantic 模型实例。
        
        Returns:
            模型实例，如果无数据或无schema则返回None
        
        Raises:
            ValidationError: 如果数据验证失败
        """
        if self.schema is None or self.data is None:
            return None
        
        return self.schema.model_validate(self.data)
    
    def from_model(self, model: T) -> None:
        """
        从 Pydantic 模型实例设置数据。
        
        Args:
            model: Pydantic 模型实例
        """
        try:
            self.data = model.model_dump()
        except AttributeError as e:
            raise ValueError(
                f"Cannot convert model to dict: model_dump not available ({type(model)})"
            ) from e
        
        # 自动设置schema
        if self.schema is None:
            self.schema = type(model)
    
    # ===== 辅助方法 =====
    
    def get_field(self, field_name: str, default: Any = None) -> Any:
        """
        从data中获取指定字段的值。
        
        Args:
            field_name: 字段名
            default: 默认值
        
        Returns:
            字段值，如果不存在则返回default
        """
        if self.data is None:
            return default
        return self.data.get(field_name, default)
    
    def set_field(self, field_name: str, value: Any) -> None:
        """
        设置data中的指定字段。
        
        Args:
            field_name: 字段名
            value: 字段值
        """
        if self.data is None:
            self.data = {}
        self.data[field_name] = value
    
    def has_field(self, field_name: str) -> bool:
        """
        检查data中是否存在指定字段。
        
        Args:
            field_name: 字段名
        
        Returns:
            True 如果字段存在
        """
        if self.data is None:
            return False
        return field_name in self.data
    
    # ===== 状态管理方法（带日志记录） =====
    
    def append_log(self, message: str) -> None:
        """追加日志到 sync_log（中文）"""
        import time
        from datetime import datetime
        timestamp = datetime.fromtimestamp(time.time()).strftime("%H:%M:%S")
        log_entry = f"[{timestamp}] {message}"
        
        if self.sync_log:
            self.sync_log += "\n" + log_entry
        else:
            self.sync_log = log_entry
    
    def set_status(self, new_status: SyncStatus, reason: str = "") -> None:
        """
        设置status并更新时间戳和日志（如果状态改变）
        
        Args:
            new_status: 新的执行状态
            reason: 状态变化原因（中文）
        """
        if self.status != new_status:
            old_status = self.status
            self.status = new_status
            reason_text = f" - {reason}" if reason else ""
            
            # DEBUG 日志
            import logging
            logger = logging.getLogger(__name__)
            logger.debug(
                f"[{self.node_type}] 节点 {self.node_id}: "
                f"执行状态 {old_status.value} → {new_status.value}{reason_text}"
            )

    def set_data_id(self, new_data_id: str, reason: str = "") -> None:
        """
        设置 data_id（仅允许在 event apply 路径内调用）
        """
        if self.data_id != new_data_id:
            old_data_id = self.data_id
            self.data_id = new_data_id

            reason_text = f" - {reason}" if reason else ""
            self.append_log(f"数据ID: {old_data_id or '<empty>'} → {new_data_id or '<empty>'}{reason_text}")

            import logging
            logger = logging.getLogger(__name__)
            logger.debug(
                f"[{self.node_type}] 节点 {self.node_id}: "
                f"data_id {old_data_id or '<empty>'} → {new_data_id or '<empty>'}{reason_text}"
            )
    
    def set_binding_status(self, new_status: BindingStatus, reason: str = "") -> None:
        """
        设置binding_status并更新时间戳和日志（如果状态改变）
        
        Args:
            new_status: 新的绑定状态
            reason: 状态变化原因（中文）
        """
        if self.binding_status != new_status:
            if self.__dict__.get("_core_guard_enabled", False):
                write_source = self.__dict__.get("_core_write_source", "")
                if write_source != "state_machine":
                    raise RuntimeError(
                        f"binding_status update must go through state machine for node={self.node_id}, source={write_source or '<none>'}"
                    )
            old_status = self.binding_status
            self.binding_status = new_status
            
            reason_text = reason or "<no-reason>"
            
            # DEBUG 日志
            import logging
            logger = logging.getLogger(__name__)
            logger.debug(
                f"[{self.node_type}] 节点 {self.node_id}: "
                f"绑定状态 {old_status.value} → {new_status.value} - {reason_text}"
            )
    
    def set_action(self, new_action: SyncAction, reason: str = "") -> None:
        """
        设置action并更新时间戳和日志（如果动作改变）
        
        Args:
            new_action: 新的同步动作
            reason: 动作变化原因（中文）
        """
        if self.action != new_action:
            old_action = self.action
            self.action = new_action
            reason_text = f" - {reason}" if reason else ""
            
            # DEBUG 日志
            import logging
            logger = logging.getLogger(__name__)
            logger.debug(
                f"[{self.node_type}] 节点 {self.node_id}: "
                f"同步动作 {old_action.value} → {new_action.value}{reason_text}"
            )
