"""SQLCipher 密钥统一存储与持久化（single source of truth）。

设计目标：
- 统一密钥来源：salt -> enc_key 映射、默认 key、已拒绝 salt、来源/验证/pid 元信息
  全部在此类管理，DbReader 与 server.py 都通过此类读写，避免双份同步问题
- 持久化：自动加载/保存 woc-keys.json，容器重启后复用
- 线程安全：内置 threading.Lock
- 进程失效检测：pid / start_time 变化时 is_valid_for_pid 返回 False

使用方式：
    key_cache = KeyCache(keys_file="/config/woc-keys.json")

    # 注入（server.py / db_reader.py 都可调用）
    key_cache.set_default_key(env_key, source="env", verified=False)
    key_cache.set_key(key_hex, salt_hex=salt, source="api", verified=True)
    key_cache.set_key_map(key_map, source="auto_extract", verified=True, pid=pid)

    # 查询（DbReader 用）
    key = key_cache.get_key(salt_hex)   # 严格按 salt，回退到未拒绝的默认 key
    has = key_cache.has_keys()
"""

from __future__ import annotations

import datetime
import json
import os
import threading
from typing import Any, Optional


class KeyCache:
    """DB 解密密钥统一存储。

    所有密钥状态（salt->key 映射、默认 key、已拒绝 salt、来源/验证/pid）
    全部在此类管理，DbReader 与 server.py 都通过此类读写，避免双份同步。
    """

    def __init__(
        self,
        keys_file: str = "/config/woc-keys.json",
    ) -> None:
        """
        Args:
            keys_file: 持久化密钥文件路径（/config/woc-keys.json）
        """
        self.keys_file = keys_file

        # 持久化的 salt -> enc_key 映射
        self._salt_to_key: dict[str, str] = {}
        # 默认 key（env 注入或单 key 兼容），不持久化（未按 salt 验证）
        self._default_key: Optional[str] = None
        # 已验证默认 key 不匹配的 salt 集合，避免反复触发整库解密失败
        self._rejected_default_salts: set[str] = set()
        # 持久化文件中记录的 wxid
        self._wxid: Optional[str] = None

        # 元信息（不持久化，进程级状态）
        self.source: Optional[str] = None  # env / api / auto_extract
        self.verified: bool = False
        self.pid: Optional[int] = None
        self.start_time: Optional[float] = None

        self._lock = threading.Lock()
        self._load_from_file()

    # ------------------------------------------------------------------
    # 文件持久化
    # ------------------------------------------------------------------
    def _load_from_file(self) -> None:
        """从 keys_file 加载密钥到内存。"""
        if not os.path.isfile(self.keys_file):
            return
        try:
            with open(self.keys_file, "r", encoding="utf-8") as f:
                data = json.load(f)
            if not isinstance(data, dict):
                return
            self._wxid = data.get("_wxid")
            self._salt_to_key = {
                k: v for k, v in data.items()
                if not k.startswith("_") and isinstance(v, str)
            }
        except (json.JSONDecodeError, OSError):
            pass

    def _save_to_file(self) -> None:
        """持久化 salt -> key 映射到 keys_file（调用方需持锁）。"""
        try:
            os.makedirs(os.path.dirname(self.keys_file), exist_ok=True)
            data: dict[str, Any] = {
                "_wxid": self._wxid,
                "_updated_at": datetime.datetime.now(datetime.timezone.utc).isoformat(),
            }
            data.update(self._salt_to_key)
            with open(self.keys_file, "w", encoding="utf-8") as f:
                json.dump(data, f, indent=2, ensure_ascii=False)
        except OSError:
            pass

    def set_wxid(self, wxid: Optional[str]) -> None:
        """更新持久化文件中的 wxid 字段。"""
        with self._lock:
            self._wxid = wxid
            self._save_to_file()

    # ------------------------------------------------------------------
    # 查询
    # ------------------------------------------------------------------
    def get_key(self, salt_hex: Optional[str] = None) -> Optional[str]:
        """返回指定 salt 的有效密钥。

        严格按 salt 查找；未命中时仅对未拒绝过的 salt 回退到默认 key，
        避免反复用错误默认 key 触发整库解密失败。
        """
        with self._lock:
            if salt_hex:
                if salt_hex in self._salt_to_key:
                    return self._salt_to_key[salt_hex]
                if self._default_key and salt_hex not in self._rejected_default_salts:
                    return self._default_key
                return None
            return self._default_key

    def get_default_key(self) -> Optional[str]:
        """返回默认密钥（向后兼容旧接口）。"""
        with self._lock:
            return self._default_key

    def get_keys(self) -> dict[str, str]:
        """返回当前持久化的 salt -> key 映射副本。"""
        with self._lock:
            return dict(self._salt_to_key)

    def has_keys(self) -> bool:
        """是否有可用的密钥（持久化或默认 key）。"""
        with self._lock:
            return bool(self._salt_to_key or self._default_key)

    def has_persisted_keys(self) -> bool:
        """是否有持久化到文件的 salt -> key 映射。"""
        with self._lock:
            return bool(self._salt_to_key)

    def key_prefix(self) -> Optional[str]:
        """返回缓存密钥的摘要（不泄露完整 key）。

        多 salt 时返回数量 + 第一个 key 的前 4/后 4 位。
        """
        with self._lock:
            if self._default_key:
                k = self._default_key
                return f"{k[:4]}...{k[-4:]}"
            if not self._salt_to_key:
                return None
            first_key = next(iter(self._salt_to_key.values()))
            prefix = f"{first_key[:4]}...{first_key[-4:]}"
            if len(self._salt_to_key) > 1:
                return f"{prefix} (+{len(self._salt_to_key) - 1} salts)"
            return prefix

    @property
    def cached(self) -> bool:
        """是否有缓存的 key（持久化或默认）。"""
        with self._lock:
            return self._default_key is not None or bool(self._salt_to_key)

    def is_valid_for_pid(
        self,
        current_pid: Optional[int],
        current_start_time: Optional[float] = None,
    ) -> bool:
        """检查缓存的 key 是否对当前进程仍有效。

        pid 变化或 start_time 变化时返回 False（需重新提取）。
        """
        if not self.cached:
            return False
        with self._lock:
            if current_pid is not None and self.pid is not None and current_pid != self.pid:
                return False
            if current_start_time is not None and self.start_time is not None and current_start_time != self.start_time:
                return False
        return True

    # ------------------------------------------------------------------
    # 注入
    # ------------------------------------------------------------------
    def set_key(
        self,
        key_hex: Optional[str],
        salt_hex: Optional[str] = None,
        source: Optional[str] = None,
        verified: bool = True,
        pid: Optional[int] = None,
        start_time: Optional[float] = None,
    ) -> None:
        """设置单个密钥。

        Args:
            key_hex: 64 位十六进制密钥；为 None 时清空所有密钥与元信息
            salt_hex: 为 None 时设默认 key，否则设 salt -> key 映射
            source: env / api / auto_extract
            verified: 是否已通过 HMAC 验证
            pid / start_time: 自动提取时关联的进程信息
        """
        with self._lock:
            if key_hex is None:
                # 清空所有
                self._salt_to_key.clear()
                self._default_key = None
                self._rejected_default_salts.clear()
                self.source = None
                self.verified = False
                self.pid = None
                self.start_time = None
                self._save_to_file()
                return

            if salt_hex is None:
                self._default_key = key_hex
            else:
                self._salt_to_key[salt_hex] = key_hex
                # 新 key 注入时清除该 salt 的拒绝标记
                self._rejected_default_salts.discard(salt_hex)
                self._save_to_file()

            if source is not None:
                self.source = source
            self.verified = verified
            if pid is not None:
                self.pid = pid
            if start_time is not None:
                self.start_time = start_time

    def set_default_key(
        self,
        key_hex: Optional[str],
        source: Optional[str] = None,
        verified: bool = False,
    ) -> None:
        """设置默认密钥（env 注入专用），不影响 salt_to_key 映射。

        与 set_key(salt_hex=None) 的区别：
        - set_key(salt_hex=None) 会清空 salt_to_key 并覆盖 woc-keys.json
        - set_default_key 仅设 _default_key，保留已持久化的多 salt 映射

        用于 WOC_DB_KEY 环境变量加载：容器重启时先从 woc-keys.json 恢复多 salt
        映射，再用 env key 作为默认兜底，二者共存而非互斥。
        """
        with self._lock:
            self._default_key = key_hex
            if source is not None:
                self.source = source
            self.verified = verified
            # 默认 key 不持久化到文件（未按 salt 验证，避免污染 woc-keys.json）

    def set_key_map(
        self,
        key_map: dict[str, str],
        source: Optional[str] = None,
        verified: bool = True,
        pid: Optional[int] = None,
        start_time: Optional[float] = None,
        persist: bool = True,
    ) -> None:
        """批量设置 salt -> enc_key 映射。

        Args:
            key_map: {salt_hex: enc_key_hex}
            source: env / api / auto_extract
            verified: 是否已通过 HMAC 验证
            pid / start_time: 自动提取时关联的进程信息
            persist: 是否持久化到文件（默认 True）
        """
        with self._lock:
            self._salt_to_key = dict(key_map)
            # 新 keymap 注入时清空拒绝标记（旧 salt 可能已重新匹配）
            self._rejected_default_salts.clear()
            if source is not None:
                self.source = source
            self.verified = verified
            if pid is not None:
                self.pid = pid
            if start_time is not None:
                self.start_time = start_time
            if persist:
                self._save_to_file()

    def clear(self) -> None:
        """清空所有密钥与元信息（含持久化文件）。"""
        with self._lock:
            self._salt_to_key.clear()
            self._default_key = None
            self._rejected_default_salts.clear()
            self.source = None
            self.verified = False
            self.pid = None
            self.start_time = None
            self._save_to_file()

    def clear_default_key(self) -> None:
        """仅清空默认 key（保留 salt_to_key 持久化映射）。

        用于 env key 验证失败后清除默认 key，不影响已持久化的多 salt 映射。
        """
        with self._lock:
            self._default_key = None
            self._rejected_default_salts.clear()

    def mark_default_key_mismatch(self, salt_hex: str) -> None:
        """标记默认 key 不匹配该 salt。

        由 DbReader._ensure_decrypted 在解密失败且使用的是默认 key 时调用，
        避免后续相同 salt 的查询反复触发整库解密。
        """
        with self._lock:
            if self._default_key:
                self._rejected_default_salts.add(salt_hex)

    def set_meta(
        self,
        source: Optional[str] = None,
        verified: Optional[bool] = None,
        pid: Optional[int] = None,
        start_time: Optional[float] = None,
    ) -> None:
        """更新元信息（不影响密钥存储）。

        用于外部已通过其他途径设置 key 后补充元信息（如 source="api"）。
        所有参数为 None 时不更新对应字段。
        """
        with self._lock:
            if source is not None:
                self.source = source
            if verified is not None:
                self.verified = verified
            if pid is not None:
                self.pid = pid
            if start_time is not None:
                self.start_time = start_time
