# -*- coding: utf-8 -*-
"""
cache_manager.py — 问题排查助手缓存管理模块

功能：
  - 基于 JSON 文件的缓存存储
  - 支持缓存过期检查
  - 支持缓存清理
  - 支持缓存统计

用法：
  from cache_manager import CacheManager

  cache = CacheManager(cache_dir='./cache', expire_hours=24)

  # 获取缓存
  cached = cache.get(project_name, system_type, apk_product, query)

  # 设置缓存
  cache.set(project_name, system_type, apk_product, query, response, matched_cases)

  # 清理过期缓存
  cache.clear_expired()
"""

import json
import hashlib
import time
import os
from pathlib import Path
from datetime import datetime

from utils.logger import get_logger

logger = get_logger(__name__)


class CacheManager:
    """问题排查结果缓存管理器"""

    def __init__(self, cache_dir, expire_hours=24, max_size_mb=100):
        """
        初始化缓存管理器。

        参数：
          cache_dir: 缓存目录
          expire_hours: 缓存过期时间（小时）
          max_size_mb: 最大缓存大小（MB），超过时自动清理最旧的缓存
        """
        self.cache_dir = Path(cache_dir)
        self.cache_dir.mkdir(parents=True, exist_ok=True)
        self.expire_seconds = expire_hours * 3600
        self.max_size_bytes = max_size_mb * 1024 * 1024

    def _get_cache_key(self, project_name, system_type, apk_product, query, model=None):
        """生成缓存键（MD5 哈希）

        缓存键含 model 维度（P3-3）：切换模型后返回各自缓存。
        model 为 None 时保持旧格式 key（兼容未传 model 的调用方）。
        """
        model_part = f"|{model}" if model else ""
        content = f"{project_name}|{system_type}|{apk_product}|{query}{model_part}"
        return hashlib.md5(content.encode('utf-8')).hexdigest()

    def get(self, project_name, system_type, apk_product, query, model=None):
        """
        获取缓存结果。

        参数：
          model: 模型 ID（可选，加入缓存键维度）

        返回：
          缓存数据字典，或 None（缓存不存在或已过期）
        """
        key = self._get_cache_key(project_name, system_type, apk_product, query, model)
        cache_file = self.cache_dir / f"{key}.json"

        if not cache_file.exists():
            return None

        try:
            with open(cache_file, 'r', encoding='utf-8') as f:
                data = json.load(f)

            # 检查过期
            if time.time() - data.get('timestamp', 0) > self.expire_seconds:
                cache_file.unlink()
                return None

            return data
        except FileNotFoundError:
            # 缓存文件已被删除（并发场景），视为未命中
            return None
        except json.JSONDecodeError as e:
            logger.error(f"缓存文件 JSON 解析失败: {cache_file} - {e}")
            return None
        except OSError as e:
            logger.error(f"读取缓存文件失败: {cache_file} - {e}")
            return None

    def set(self, project_name, system_type, apk_product, query, response, matched_cases, model=None):
        """
        设置缓存。

        参数：
          project_name: 项目名称
          system_type: 系统类型
          apk_product: APK 产品
          query: 问题描述
          response: AI 响应内容
          matched_cases: 匹配案例列表
          model: 模型 ID（可选，加入缓存键维度）
        """
        key = self._get_cache_key(project_name, system_type, apk_product, query, model)
        cache_file = self.cache_dir / f"{key}.json"

        data = {
            'timestamp': time.time(),
            'datetime': datetime.now().isoformat(),
            'project_name': project_name,
            'system_type': system_type,
            'apk_product': apk_product,
            'query': query,
            'response': response,
            'matched_cases': matched_cases,
        }

        try:
            with open(cache_file, 'w', encoding='utf-8') as f:
                json.dump(data, f, ensure_ascii=False, indent=2)
            print(f"[缓存] 已缓存: {key[:8]}...")
        except (TypeError, ValueError) as e:
            logger.error(f"缓存数据序列化失败: {cache_file} - {e}")
        except OSError as e:
            logger.error(f"写入缓存文件失败: {cache_file} - {e}")

    def clear_expired(self):
        """清理过期缓存"""
        cleared = 0
        for cache_file in self.cache_dir.glob("*.json"):
            try:
                with open(cache_file, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                if time.time() - data.get('timestamp', 0) > self.expire_seconds:
                    cache_file.unlink()
                    cleared += 1
            except (json.JSONDecodeError, OSError, KeyError) as e:
                # 单个缓存文件损坏或并发删除，跳过该文件，不中断清理
                logger.debug(f"清理缓存时跳过损坏文件 {cache_file}: {e}")

        if cleared > 0:
            print(f"[缓存] 已清理 {cleared} 个过期缓存")
        return cleared

    def clear_all(self):
        """清空所有缓存"""
        cleared = 0
        for cache_file in self.cache_dir.glob("*.json"):
            cache_file.unlink()
            cleared += 1
        print(f"[缓存] 已清空 {cleared} 个缓存")
        return cleared

    def get_stats(self):
        """获取缓存统计信息"""
        total_files = 0
        total_size = 0
        oldest_time = None
        newest_time = None

        for cache_file in self.cache_dir.glob("*.json"):
            total_files += 1
            total_size += cache_file.stat().st_size

            try:
                with open(cache_file, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                ts = data.get('timestamp', 0)
                if oldest_time is None or ts < oldest_time:
                    oldest_time = ts
                if newest_time is None or ts > newest_time:
                    newest_time = ts
            except (json.JSONDecodeError, OSError, KeyError) as e:
                # 统计场景：损坏文件不影响整体统计，跳过
                logger.debug(f"统计缓存时跳过损坏文件 {cache_file}: {e}")

        return {
            'total_files': total_files,
            'total_size_mb': round(total_size / (1024 * 1024), 2),
            'oldest': datetime.fromtimestamp(oldest_time).isoformat() if oldest_time else None,
            'newest': datetime.fromtimestamp(newest_time).isoformat() if newest_time else None,
        }

    def check_and_clean_if_needed(self):
        """检查缓存大小，超过限制时清理最旧的缓存"""
        total_size = sum(f.stat().st_size for f in self.cache_dir.glob("*.json"))

        if total_size > self.max_size_bytes:
            print(f"[缓存] 缓存大小 {total_size / (1024*1024):.2f}MB 超过限制，清理最旧的缓存...")

            # 获取所有缓存文件及其时间戳
            cache_files = []
            for cache_file in self.cache_dir.glob("*.json"):
                try:
                    with open(cache_file, 'r', encoding='utf-8') as f:
                        data = json.load(f)
                    cache_files.append((cache_file, data.get('timestamp', 0)))
                except (json.JSONDecodeError, OSError, KeyError) as e:
                    # 损坏文件时间戳按 0 处理（视为最旧，优先清理）
                    logger.debug(f"按时间戳排序时跳过损坏文件 {cache_file}: {e}")
                    cache_files.append((cache_file, 0))

            # 按时间戳排序，删除最旧的 20%
            cache_files.sort(key=lambda x: x[1])
            delete_count = max(1, len(cache_files) // 5)

            for cache_file, _ in cache_files[:delete_count]:
                cache_file.unlink()

            print(f"[缓存] 已清理 {delete_count} 个最旧缓存")


# 模块级单例
_cache_manager = None


def get_cache_manager():
    """获取缓存管理器单例"""
    global _cache_manager
    if _cache_manager is None:
        from pathlib import Path
        cache_dir = Path(__file__).resolve().parent / "cache"
        _cache_manager = CacheManager(cache_dir, expire_hours=24)
    return _cache_manager


if __name__ == "__main__":
    # 测试
    cache = CacheManager("./test_cache", expire_hours=1)

    # 测试设置缓存
    cache.set("厦门银行", "标准版预定2.0", "门口屏5.0", "MQTT连接失败",
              "这是测试响应", [{"rank": 1, "score": 0.9}])

    # 测试获取缓存
    result = cache.get("厦门银行", "标准版预定2.0", "门口屏5.0", "MQTT连接失败")
    print(f"缓存命中: {result is not None}")

    # 测试统计
    stats = cache.get_stats()
    print(f"缓存统计: {stats}")

    # 清理测试缓存
    cache.clear_all()
