"""
脱敏字典管理器

负责脱敏字典的加密存储、读取和生命周期管理。
本地保留脱敏字典，支持将云端审查结果还原为真实工程术语。

根据 wlast.md 文档第6.3.1节和附录A设计
"""

import json
import hashlib
import logging
from typing import Dict, Any, Optional
from datetime import datetime
from pathlib import Path

try:
    from cryptography.fernet import Fernet
    from cryptography.hazmat.primitives import hashes
    from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
    import base64
    CRYPTO_AVAILABLE = True
except ImportError:
    CRYPTO_AVAILABLE = False

from foundation.infrastructure.config.config import config_handler
from foundation.infrastructure.cache.redis_connection import RedisConnectionFactory

logger = logging.getLogger(__name__)


class DictManager:
    """脱敏字典管理器

    功能：
    1. 脱敏字典的加密存储（本地文件 + Redis缓存）
    2. 脱敏字典的读取和解密
    3. 脱敏字典的生命周期管理（自动清理）
    4. 多版本备份支持
    """

    # Redis 键前缀
    REDIS_KEY_PREFIX = "desensitize:dict:"
    REDIS_TTL = 7 * 24 * 3600  # 7天过期

    def __init__(self):
        """初始化字典管理器"""
        self.cfg = config_handler.get_section("desensitize")
        self.storage_path = Path(self.cfg.get("DICT_STORAGE_PATH", "./data/desensitize_dicts"))
        self.storage_path.mkdir(parents=True, exist_ok=True)

        # 初始化加密（如果配置了密钥）
        self.encryption_key = self._get_encryption_key()
        self.cipher = None
        if self.encryption_key and CRYPTO_AVAILABLE:
            self.cipher = Fernet(self.encryption_key)
        elif self.encryption_key and not CRYPTO_AVAILABLE:
            logger.warning("[DictManager] cryptography 库未安装，加密功能不可用")

    def _get_encryption_key(self) -> Optional[bytes]:
        """获取加密密钥"""
        key_str = self.cfg.get("DICT_ENCRYPTION_KEY", "")

        # 如果配置了 KMS 引用或环境变量
        if key_str.startswith("${") and key_str.endswith("}"):
            env_var = key_str[2:-1]
            import os
            key_str = os.environ.get(env_var, "")

        if not key_str:
            # 使用默认密钥（仅开发环境）
            logger.warning("[DictManager] 使用默认加密密钥，生产环境请配置安全密钥")
            key_str = "LQAgentPlatform-Desensitize-Default-Key-32bytes"

        # 生成 Fernet 密钥
        if CRYPTO_AVAILABLE:
            kdf = PBKDF2HMAC(
                algorithm=hashes.SHA256(),
                length=32,
                salt=b"lqagentplatform_desensitize_salt",
                iterations=100000,
            )
            key = base64.urlsafe_b64encode(kdf.derive(key_str.encode()))
            return key
        return None

    async def save(self, task_id: str, mapping_dict: Dict[str, Any]) -> str:
        """保存脱敏字典

        Args:
            task_id: 任务唯一标识
            mapping_dict: 脱敏映射字典

        Returns:
            dict_hash: 字典哈希值（用于验证完整性）
        """
        # 1. 构造完整字典
        full_dict = {
            "version": "1.0",
            "created_at": datetime.now().isoformat(),
            "task_id": task_id,
            "mappings": mapping_dict,
        }

        # 2. 计算哈希
        dict_content = json.dumps(full_dict, sort_keys=True, ensure_ascii=False)
        dict_hash = hashlib.sha256(dict_content.encode()).hexdigest()[:16]
        full_dict["dict_hash"] = dict_hash

        # 3. 加密（如果可用）
        if self.cipher:
            encrypted_content = self.cipher.encrypt(dict_content.encode())
            storage_content = encrypted_content
            storage_format = "encrypted"
        else:
            storage_content = dict_content.encode()
            storage_format = "plain"

        # 4. 本地文件存储
        file_path = self.storage_path / f"{task_id}.dict"
        try:
            with open(file_path, "wb") as f:
                f.write(storage_content)
            logger.info(f"[DictManager] 脱敏字典已保存: {file_path}")
        except Exception as e:
            logger.error(f"[DictManager] 本地存储失败: {e}")
            raise

        # 5. Redis 缓存（便于快速访问）
        try:
            redis_conn = await RedisConnectionFactory.get_connection()
            redis_key = f"{self.REDIS_KEY_PREFIX}{task_id}"
            await redis_conn.setex(
                redis_key,
                self.REDIS_TTL,
                json.dumps({
                    "format": storage_format,
                    "hash": dict_hash,
                    "content": base64.b64encode(storage_content).decode() if isinstance(storage_content, bytes) else storage_content
                })
            )
        except Exception as e:
            logger.warning(f"[DictManager] Redis 缓存失败: {e}")

        return dict_hash

    async def load(self, task_id: str) -> Optional[Dict[str, Any]]:
        """加载脱敏字典

        Args:
            task_id: 任务唯一标识

        Returns:
            脱敏字典，如果不存在则返回 None
        """
        # 1. 尝试从 Redis 加载
        try:
            redis_conn = await RedisConnectionFactory.get_connection()
            redis_key = f"{self.REDIS_KEY_PREFIX}{task_id}"
            cached = await redis_conn.get(redis_key)
            if cached:
                data = json.loads(cached)
                storage_content = base64.b64decode(data["content"])

                if data["format"] == "encrypted" and self.cipher:
                    decrypted = self.cipher.decrypt(storage_content)
                    return json.loads(decrypted.decode())
                else:
                    return json.loads(storage_content.decode())
        except Exception as e:
            logger.warning(f"[DictManager] Redis 加载失败: {e}")

        # 2. 从本地文件加载
        file_path = self.storage_path / f"{task_id}.dict"
        if not file_path.exists():
            logger.error(f"[DictManager] 字典文件不存在: {file_path}")
            return None

        try:
            with open(file_path, "rb") as f:
                storage_content = f.read()

            # 解密
            if self.cipher:
                try:
                    decrypted = self.cipher.decrypt(storage_content)
                    return json.loads(decrypted.decode())
                except Exception as e:
                    logger.error(f"[DictManager] 解密失败: {e}")
                    # 尝试作为明文解析
                    return json.loads(storage_content.decode())
            else:
                return json.loads(storage_content.decode())

        except Exception as e:
            logger.error(f"[DictManager] 文件加载失败: {e}")
            return None

    async def get_mapping(self, task_id: str) -> Optional[Dict[str, Any]]:
        """获取映射关系（简化接口）"""
        full_dict = await self.load(task_id)
        if full_dict:
            return full_dict.get("mappings", {})
        return None

    async def delete(self, task_id: str) -> bool:
        """删除脱敏字典"""
        success = True

        # 删除本地文件
        file_path = self.storage_path / f"{task_id}.dict"
        try:
            if file_path.exists():
                file_path.unlink()
                logger.info(f"[DictManager] 本地字典已删除: {file_path}")
        except Exception as e:
            logger.error(f"[DictManager] 本地删除失败: {e}")
            success = False

        # 删除 Redis 缓存
        try:
            redis_conn = await RedisConnectionFactory.get_connection()
            redis_key = f"{self.REDIS_KEY_PREFIX}{task_id}"
            await redis_conn.delete(redis_key)
        except Exception as e:
            logger.warning(f"[DictManager] Redis 删除失败: {e}")

        return success

    async def exists(self, task_id: str) -> bool:
        """检查字典是否存在"""
        file_path = self.storage_path / f"{task_id}.dict"
        return file_path.exists()

    def get_dict_metadata(self, task_id: str) -> Optional[Dict[str, Any]]:
        """获取字典元信息（不加载完整内容）"""
        file_path = self.storage_path / f"{task_id}.dict"
        if not file_path.exists():
            return None

        try:
            stat = file_path.stat()
            return {
                "task_id": task_id,
                "file_path": str(file_path),
                "file_size": stat.st_size,
                "modified_at": datetime.fromtimestamp(stat.st_mtime).isoformat(),
                "exists": True
            }
        except Exception as e:
            logger.error(f"[DictManager] 获取元信息失败: {e}")
            return None

    async def cleanup_expired(self, days: int = 7) -> int:
        """清理过期字典

        Args:
            days: 过期天数

        Returns:
            清理的字典数量
        """
        import time
        cutoff_time = time.time() - (days * 24 * 3600)
        cleaned = 0

        for dict_file in self.storage_path.glob("*.dict"):
            try:
                stat = dict_file.stat()
                if stat.st_mtime < cutoff_time:
                    dict_file.unlink()
                    cleaned += 1
                    logger.info(f"[DictManager] 清理过期字典: {dict_file.name}")
            except Exception as e:
                logger.warning(f"[DictManager] 清理失败 {dict_file}: {e}")

        return cleaned

    async def create_backup(self, task_id: str) -> Optional[str]:
        """创建字典备份"""
        file_path = self.storage_path / f"{task_id}.dict"
        if not file_path.exists():
            return None

        backup_path = self.storage_path / f"{task_id}.dict.backup"
        try:
            import shutil
            shutil.copy2(file_path, backup_path)
            return str(backup_path)
        except Exception as e:
            logger.error(f"[DictManager] 备份失败: {e}")
            return None
