"""
FlaxKV2 LMDB 字典基类

提取 RawLmdbDict 和 CachedLmdbDict 的公共代码，减少代码重复。
"""

import os
import struct
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Tuple, Optional, Iterator

from flaxkv2.serialization import encoder, decoder
from flaxkv2.serialization.value_meta import ValueWithMeta
from flaxkv2.core.nested_structures import NestedDBDict, NestedDBList
from flaxkv2.utils.log import get_logger
from flaxkv2.instance_manager import db_instance_manager
from flaxkv2.display import DisplayMixin

logger = get_logger(__name__)


class BaseLmdbDict(DisplayMixin, ABC):
    """
    LMDB 字典基类

    提供公共的：
    - 实例管理（__new__）
    - 键值编解码
    - TTL 管理
    - 嵌套结构支持
    - 上下文管理器
    """

    # ========== 实例管理 ==========

    def __new__(
        cls,
        name: str,
        path: str = ".",
        rebuild: bool = False,
        **kwargs
    ):
        """
        创建或返回数据库实例

        如果数据库已经打开，返回已有实例；否则创建新实例。
        如果rebuild=True，则关闭旧实例并创建新实例。
        """
        # 计算数据库路径
        abs_path = os.path.abspath(path)
        db_path = os.path.join(abs_path, name)

        # 如果需要重建，先关闭并删除旧实例
        if rebuild:
            existing = db_instance_manager.get_instance(db_path)
            if existing is not None:
                logger.debug(f"rebuild=True，关闭并删除旧实例: {db_path}")
                try:
                    # 先从缓存移除（但不调用close，因为我们需要手动删除文件）
                    db_instance_manager.unregister_instance(db_path)
                    if hasattr(existing, '_db') and existing._db is not None:
                        existing._db.close()
                    existing._closed = True
                except Exception as e:
                    logger.warning(f"关闭旧实例时出错: {e}")

            # 删除数据库文件（无论是否有缓存实例）
            if os.path.exists(db_path):
                import shutil
                try:
                    shutil.rmtree(db_path)
                    logger.debug(f"已删除旧数据库文件: {db_path}")
                except Exception as e:
                    logger.error(f"删除数据库文件失败: {e}")

            # 创建新实例
            instance = super().__new__(cls)
            instance._is_new_instance = True
            return instance

        # 检查是否已有实例
        existing = db_instance_manager.get_instance(db_path)
        if existing is not None:
            logger.debug(f"返回已存在的数据库实例: {db_path}")
            # 返回已有实例，不需要再次初始化
            return existing

        # 创建新实例
        instance = super().__new__(cls)
        instance._is_new_instance = True
        return instance

    # ========== 抽象方法（子类必须实现） ==========

    @abstractmethod
    def __getitem__(self, key) -> Any:
        """获取键值"""
        pass

    @abstractmethod
    def __setitem__(self, key, value) -> None:
        """设置键值"""
        pass

    @abstractmethod
    def __delitem__(self, key) -> None:
        """删除键"""
        pass

    @abstractmethod
    def set(self, key, value, ttl=None) -> None:
        """设置键值（支持TTL）"""
        pass

    @abstractmethod
    def keys(self) -> List:
        """获取所有键列表"""
        pass

    @abstractmethod
    def close(self) -> None:
        """关闭数据库"""
        pass

    @abstractmethod
    def _get_backend_name(self) -> str:
        """返回后端名称（用于 stat 等方法）"""
        pass

    # ========== 键值编解码 ==========

    def _encode_key(self, key) -> bytes:
        """编码键"""
        if self._raw:
            if isinstance(key, str):
                return key.encode('utf-8')
            elif isinstance(key, bytes):
                return key
            else:
                return str(key).encode('utf-8')
        else:
            return encoder.encode_key(key)

    def _decode_key(self, key_bytes: bytes):
        """解码键"""
        if self._raw:
            try:
                return key_bytes.decode('utf-8')
            except UnicodeDecodeError:
                return key_bytes
        else:
            return decoder.decode_key(key_bytes)

    def _encode_value(self, value, ttl_seconds=None) -> bytes:
        """
        编码值（支持内嵌TTL）

        Args:
            value: 要编码的值
            ttl_seconds: TTL秒数（None表示无TTL）

        Returns:
            bytes: 编码后的字节流
        """
        if self._raw:
            # Raw模式：不支持TTL
            if ttl_seconds is not None:
                raise ValueError("Raw mode does not support TTL")
            if isinstance(value, str):
                return value.encode('utf-8')
            elif isinstance(value, bytes):
                return value
            else:
                return str(value).encode('utf-8')
        else:
            # 使用ValueWithMeta进行编码（内嵌TTL）
            return ValueWithMeta.encode_value(value, ttl_seconds)

    def _decode_value(self, value_bytes: bytes) -> Tuple[Any, Optional[float], bool]:
        """
        解码值（提取内嵌TTL信息）

        Args:
            value_bytes: 编码后的字节流

        Returns:
            (value, expire_time, is_expired) 三元组:
            - value: 解码后的值
            - expire_time: 过期时间戳（None表示无TTL）
            - is_expired: 是否已过期
        """
        if self._raw:
            # Raw模式：不支持TTL
            try:
                value = value_bytes.decode('utf-8')
            except UnicodeDecodeError:
                value = value_bytes
            return value, None, False
        else:
            # 检查是否是新格式（带元数据）
            if ValueWithMeta.has_meta(value_bytes):
                # 新格式：使用ValueWithMeta解码
                return ValueWithMeta.decode_value(value_bytes)
            else:
                # 旧格式：直接解码（无TTL）
                value = decoder.decode(value_bytes)
                return value, None, False

    def _should_skip_internal_key(self, key_bytes: bytes) -> bool:
        """
        检查键是否是内部键（应该在 keys()/values()/items() 中跳过）

        内部键包括：
        1. __nested__: 前缀的标记键
        2. __list__: 前缀的标记键
        3. 嵌套存储的子键

        Returns:
            True: 应该跳过（内部键或嵌套存储的子键）
            False: 不应该跳过（用户键）
        """
        # 方法1：先尝试用 _decode_key 解码（适用于带类型前缀的键）
        try:
            decoded_key = self._decode_key(key_bytes)
            # 检查是否是内部标记键
            if isinstance(decoded_key, str) and (
                decoded_key.startswith('__nested__:') or
                decoded_key.startswith('__list__:')
            ):
                return True
            # 正常的用户键
            return False
        except (ValueError, UnicodeDecodeError, struct.error):
            # 解码失败，可能是嵌套存储的子键（没有类型前缀）
            pass

        # 方法2：尝试直接 UTF-8 解码（适用于嵌套子键）
        try:
            key_str = key_bytes.decode('utf-8')
            # 嵌套存储的子键通常包含 ':'（如 'config:database:host'）
            # 且不以 '__' 开头（内部标记键已经在上面处理了）
            if ':' in key_str and not key_str.startswith('__'):
                return True
        except UnicodeDecodeError:
            # UTF-8 解码也失败，可能是二进制数据，保守处理：不跳过
            pass

        return False

    # ========== 公共接口方法 ==========

    def __contains__(self, key) -> bool:
        """检查键是否存在"""
        try:
            self[key]
            return True
        except KeyError:
            return False

    def get(self, key, default=None):
        """获取键值，不存在返回默认值"""
        try:
            return self[key]
        except KeyError:
            return default

    def batch_get(self, keys: list) -> list:
        """批量获取多个键值，未找到为 None"""
        return [self.get(k) for k in keys]

    def batch_set(self, items: dict, ttl: Optional[int] = None) -> None:
        """批量设置多个键值"""
        for k, v in items.items():
            self.set(k, v, ttl=ttl)

    # ========== 缓存钩子（CachedLmdbDict 重写） ==========

    def _get_cache_dirty_keys(self) -> set:
        """返回缓存中的脏键集合（子类重写）"""
        return set()

    def _get_cache_deleted_keys(self) -> set:
        """返回缓存中已删除的键集合（子类重写）"""
        return set()

    # ========== 迭代器 ==========

    def _key_matches_filter(self, key, prefix=None, start=None, stop=None) -> bool:
        """检查键是否匹配过滤条件"""
        if prefix is not None:
            return isinstance(key, str) and key.startswith(prefix)
        if start is not None and key < start:
            return False
        if stop is not None and key >= stop:
            return False
        return True

    def keys_iter(self, prefix=None, start=None, stop=None, reverse=False) -> Iterator:
        """
        流式迭代键（支持范围扫描和反向迭代）

        Args:
            prefix: 键前缀过滤（与 start/stop 互斥）
            start: 范围起始键（包含）
            stop: 范围结束键（不包含）
            reverse: 是否反向迭代

        Yields:
            键（按排序顺序）

        Examples:
            >>> for key in db.keys_iter():
            ...     print(key)
            >>> for key in db.keys_iter(start='b', stop='d'):
            ...     print(key)
            >>> for key in db.keys_iter(prefix='user:', reverse=True):
            ...     print(key)
        """
        if prefix is not None and (start is not None or stop is not None):
            raise ValueError("prefix and start/stop are mutually exclusive")

        # Raw 模式：无标记键、无类型前缀
        if self._raw:
            iter_kwargs = {'reverse': reverse}
            if prefix is not None:
                iter_kwargs['prefix'] = prefix.encode('utf-8') if isinstance(prefix, str) else prefix
            if start is not None:
                iter_kwargs['start'] = self._encode_key(start)
            if stop is not None:
                iter_kwargs['stop'] = self._encode_key(stop)
            for key_bytes, _ in self._db.iterator(**iter_kwargs):
                key = self._decode_key(key_bytes)
                yield key
            return

        # 非 raw 模式：需要处理标记键、嵌套子键、TTL
        from flaxkv2.serialization.value_meta import ValueWithMeta

        NESTED_MARKER = b's__nested__:'
        LIST_MARKER = b's__list__:'

        # Phase 1: 收集嵌套前缀信息和有效的标记键
        nested_prefixes_bytes = set()
        marker_keys = set()

        for marker_pfx in (NESTED_MARKER, LIST_MARKER):
            pfx_len = len(marker_pfx)
            for kb, vb in self._db.iterator(prefix=marker_pfx):
                akb = kb[pfx_len:]
                nested_prefixes_bytes.add(akb + b':')

                # 跳过子标记（如 __nested__:user:config 是 user 的子结构）
                colon_pos = akb.find(b':')
                if colon_pos > 0 and akb[:colon_pos + 1] in nested_prefixes_bytes:
                    continue

                if ValueWithMeta.is_expired_fast(vb):
                    continue

                try:
                    actual_key = akb.decode('utf-8')
                except UnicodeDecodeError:
                    continue

                if self._key_matches_filter(actual_key, prefix, start, stop):
                    marker_keys.add(actual_key)

        # Phase 2: 扫描普通键（利用 LMDB range iterator）
        iter_kwargs = {}
        if prefix is not None:
            iter_kwargs['prefix'] = self._encode_key(prefix)
        elif start is not None or stop is not None:
            if start is not None:
                iter_kwargs['start'] = self._encode_key(start)
            if stop is not None:
                iter_kwargs['stop'] = self._encode_key(stop)

        plain_keys = set()
        for key_bytes, value_bytes in self._db.iterator(**iter_kwargs):
            if key_bytes.startswith(NESTED_MARKER) or key_bytes.startswith(LIST_MARKER):
                continue
            if nested_prefixes_bytes:
                colon_pos = key_bytes.find(b':')
                if colon_pos > 0 and key_bytes[:colon_pos + 1] in nested_prefixes_bytes:
                    continue
            try:
                key = self._decode_key(key_bytes)
            except (ValueError, UnicodeDecodeError, struct.error):
                continue
            if key in marker_keys:
                continue
            if ValueWithMeta.is_expired_fast(value_bytes):
                continue
            plain_keys.add(key)

        # Phase 3: 合并、缓存调整、排序、yield
        all_keys = marker_keys | plain_keys

        # 缓存层的脏键和已删除键
        for key in self._get_cache_dirty_keys():
            if self._key_matches_filter(key, prefix, start, stop):
                all_keys.add(key)
        all_keys -= self._get_cache_deleted_keys()

        yield from sorted(all_keys, reverse=reverse)

    def items_iter(self, prefix=None, start=None, stop=None, reverse=False) -> Iterator[Tuple]:
        """
        流式迭代键值对

        参数与 keys_iter 相同。

        Yields:
            (key, value) 元组
        """
        for key in self.keys_iter(prefix=prefix, start=start, stop=stop, reverse=reverse):
            yield key, self[key]

    def values_iter(self, prefix=None, start=None, stop=None, reverse=False) -> Iterator:
        """
        流式迭代值

        参数与 keys_iter 相同。

        Yields:
            值
        """
        for key in self.keys_iter(prefix=prefix, start=start, stop=stop, reverse=reverse):
            yield self[key]

    def keys_count(self) -> int:
        """
        快速计算键数量（不加载所有键到内存）

        比 len(db) 更高效，因为不需要解码所有键。

        Returns:
            键的数量
        """
        return sum(1 for _ in self.keys_iter())

    def values(self) -> List:
        """获取所有值列表"""
        return [self[key] for key in self.keys()]

    def items(self) -> List[Tuple]:
        """获取所有键值对列表"""
        return [(key, self[key]) for key in self.keys()]

    def __len__(self) -> int:
        """返回数据库大小"""
        return len(self.keys())

    def to_dict(self) -> Dict:
        """
        转换为普通字典

        注意：NestedDBDict/NestedDBList 会递归转换为普通 dict/list
        """
        result = {}
        for k, v in self.items():
            if isinstance(v, NestedDBDict):
                v = v.to_dict()
            elif isinstance(v, NestedDBList):
                v = v.to_list()
            result[k] = v
        return result

    # ========== 字符串表示 ==========

    def _get_display_info(self) -> dict:
        """
        返回展示信息（供 DisplayMixin 使用）

        Returns:
            dict 包含展示所需的所有信息
        """
        info = {
            'class_name': self.__class__.__name__,
            'name': self.name,
            'location': self.db_path,
            'closed': self._closed,
            'extras': {},
            'tags': [],
        }

        # 添加缓存相关信息（CachedLmdbDict）
        # 检测统一缓存系统
        if hasattr(self, '_cache_enabled') and self._cache_enabled:
            info['tags'].append('cached')
            # 注意：使用 `is not None` 而不是 `if self._cache`
            # 因为 UnifiedCache 实现了 __bool__，空缓存会返回 False
            if hasattr(self, '_cache') and self._cache is not None:
                # UnifiedCache 使用 _maxsize 属性
                cache_size = getattr(self._cache, '_maxsize', None)
                if cache_size:
                    info['extras']['cache'] = cache_size

        # 检测写缓冲模式
        if hasattr(self, '_write_buffer_mode') and self._write_buffer_mode:
            info['tags'].append('buffered')

        # 检测异步 flush 模式
        if hasattr(self, '_async_flush') and self._async_flush:
            info['tags'].append('async')

        # 添加 TTL 信息
        if hasattr(self, '_default_ttl') and self._default_ttl:
            info['extras']['ttl'] = self._default_ttl
            info['tags'].append('ttl')

        return info

    # ========== 上下文管理器 ==========

    def __enter__(self):
        """上下文管理器入口"""
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """上下文管理器退出"""
        self.close()
        return False

    # ========== TTL 管理 ==========

    def set_ttl(self, key: Any, ttl_seconds: int) -> None:
        """
        为指定键设置TTL（需要重新编码值）

        Args:
            key: 键
            ttl_seconds: TTL时间（秒）
        """
        try:
            current_value = self[key]
        except KeyError:
            raise KeyError(f"Cannot set TTL for non-existent key: {key}")
        self.set(key, current_value, ttl=ttl_seconds)

    def remove_ttl(self, key: Any) -> None:
        """
        移除指定键的TTL（需要重新编码值）

        Args:
            key: 键
        """
        try:
            current_value = self[key]
        except KeyError:
            return
        self.set(key, current_value, ttl=None)

    def get_default_ttl(self) -> Optional[int]:
        """获取默认TTL设置"""
        return self._default_ttl

    def set_default_ttl(self, ttl_seconds: Optional[int]) -> None:
        """设置默认TTL"""
        self._default_ttl = ttl_seconds

    # ========== 维护方法 ==========

    def compact(self, start=None, stop=None) -> None:
        """
        触发数据压缩

        LMDB 使用 B+树，无需手动 compact（不像 LevelDB 的 LSM 树）。
        此方法保留是为了 API 兼容性。
        """
        pass

    # ========== 统计信息 ==========

    def stat(self) -> Dict:
        """
        返回数据库统计信息

        Returns:
            包含统计信息的字典
        """
        stats = {
            'path': self.db_path,
            'backend': self._get_backend_name()
        }

        try:
            db_stat = self._db.stat()
            db_info = self._db.info()
            stats['lmdb_stat'] = db_stat
            stats['lmdb_info'] = db_info

            # 近似大小
            total_pages = (
                db_stat.get('branch_pages', 0) +
                db_stat.get('leaf_pages', 0) +
                db_stat.get('overflow_pages', 0)
            )
            approximate_size = total_pages * db_stat.get('psize', 4096)
            stats['approximate_size_bytes'] = approximate_size
            if approximate_size < 1024:
                stats['approximate_size'] = f"{approximate_size} B"
            elif approximate_size < 1024 * 1024:
                stats['approximate_size'] = f"{approximate_size / 1024:.2f} KB"
            elif approximate_size < 1024 * 1024 * 1024:
                stats['approximate_size'] = f"{approximate_size / (1024 * 1024):.2f} MB"
            else:
                stats['approximate_size'] = f"{approximate_size / (1024 * 1024 * 1024):.2f} GB"

            stats['entries'] = db_stat.get('entries', 'unknown')
            stats['map_size'] = db_info.get('map_size', 'unknown')
        except Exception as e:
            logger.warning(f"Failed to get LMDB stats: {e}")

        try:
            count = len(self.keys())
            stats['count'] = count
        except Exception as e:
            logger.warning(f"Failed to count keys: {e}")
            stats['count'] = 'unknown'

        return stats

    # ========== 嵌套结构支持 ==========

    def nested(self, prefix: str) -> NestedDBDict:
        """
        创建一个基于前缀的嵌套字典视图

        Args:
            prefix: 前缀字符串，建议使用冒号分隔（如 'user:1'）

        Returns:
            NestedDBDict: 嵌套字典对象
        """
        if self._db is None:
            raise RuntimeError("Database is not open")

        prefix_with_colon = f"{prefix}:"
        prefix_bytes = prefix_with_colon.encode('utf-8')
        prefixed_db = self._db.prefixed_db(prefix_bytes)

        return NestedDBDict(prefixed_db, prefix_with_colon, parent_db=None, root_db=self)

    def nested_list(self, prefix: str) -> NestedDBList:
        """
        创建一个基于前缀的嵌套列表视图

        Args:
            prefix: 前缀字符串，建议使用冒号分隔（如 'items'）

        Returns:
            NestedDBList: 嵌套列表对象
        """
        if self._db is None:
            raise RuntimeError("Database is not open")

        prefix_with_colon = f"{prefix}:"
        prefix_bytes = prefix_with_colon.encode('utf-8')
        prefixed_db = self._db.prefixed_db(prefix_bytes)

        return NestedDBList(prefixed_db, prefix_with_colon, parent_db=None, root_db=self)
