<?php

namespace XZD\AI\V5\Core;

/**
 * AI 响应缓存层 — 基于内容哈希的智能缓存
 *
 * 目的：相同内容（或高度相似内容）的 AI 调用结果缓存，避免重复调用浪费 Token。
 *
 * 缓存策略：
 * 1. 内容哈希匹配：对输入内容计算 MD5，相同哈希命中直接返回
 * 2. 前缀匹配：对于需要缓存的 prompt 前缀（如 system prompt），用前缀+内容哈希
 * 3. TTL 管理：不同类型不同 TTL（标签7天、摘要30天、SEO 30天、翻译30天）
 * 4. 缓存失效：文章内容变更时自动失效（通过 post_modified 时间戳比对）
 *
 * 存储后端：wp_options 中的 xzd_ai_response_cache 单键（避免污染 postmeta）
 *           超大内容改用 postmeta 按文章隔离
 *
 * 缓存命中时跳过 API 调用，input token 100% 节省
 */
class AiResponseCache
{
    private const OPTION_KEY = 'xzd_ai_response_cache';
    private const MAX_CACHE_SIZE = 500; // 最多缓存500条（防止option过大）
    private const GC_PROBABILITY = 0.05; // 5%概率触发GC

    /** @var array|null 内存缓存 */
    private static ?array $memoryCache = null;
    private static bool $loaded = false;

    // ===== 缓存类型与TTL =====

    public const TYPE_TAGS = 'tags';
    public const TYPE_SUMMARY = 'summary';
    public const TYPE_SEO_TITLE = 'seo_title';
    public const TYPE_SEO_DESC = 'seo_desc';
    public const TYPE_TRANSLATE = 'translate';
    public const TYPE_CATEGORY = 'category';
    public const TYPE_KEYWORDS = 'keywords';
    public const TYPE_SENTIMENT = 'sentiment';
    public const TYPE_CUSTOM = 'custom';

    /** TTL映射（秒） */
    private const TTL = [
        self::TYPE_TAGS       => 604800,  // 7天
        self::TYPE_SUMMARY    => 2592000, // 30天
        self::TYPE_SEO_TITLE  => 2592000, // 30天
        self::TYPE_SEO_DESC   => 2592000, // 30天
        self::TYPE_TRANSLATE  => 2592000, // 30天
        self::TYPE_CATEGORY   => 604800,  // 7天
        self::TYPE_KEYWORDS   => 604800,  // 7天
        self::TYPE_SENTIMENT  => 604800,  // 7天
        self::TYPE_CUSTOM     => 3600,    // 1小时
    ];

    // ===== 公共API =====

    /**
     * 尝试从缓存获取 AI 响应
     *
     * @param string $type      缓存类型（TYPE_* 常量）
     * @param string $content   输入内容（用于计算哈希）
     * @param string $extraKey  额外标识（如翻译目标语言），可选
     * @param int    $postId    关联文章ID，用于 post_modified 校验
     * @return array|null       缓存命中返回 AI 响应数组，未命中返回 null
     */
    public static function get(string $type, string $content, string $extraKey = '', int $postId = 0): ?array
    {
        $hash = self::hash($type, $content, $extraKey);
        $cache = self::load();

        if (!isset($cache[$hash])) {
            return null;
        }

        $entry = $cache[$hash];

        // 检查 TTL
        $ttl = self::TTL[$type] ?? 3600;
        if (time() - $entry['t'] > $ttl) {
            unset($cache[$hash]);
            self::$memoryCache = $cache;
            return null;
        }

        // 检查文章内容是否变更（post_modified > cache_time → 失效）
        if ($postId > 0 && !empty($entry['post_modified'])) {
            $currentModified = get_post_modified_time('U', true, $postId);
            if ($currentModified && $currentModified > strtotime($entry['post_modified'])) {
                // 文章在缓存后修改过，缓存失效
                unset($cache[$hash]);
                self::$memoryCache = $cache;
                Logger::info('ai_cache', "缓存失效(post_modified): post={$postId}, type={$type}");
                return null;
            }
        }

        Logger::info('ai_cache', "缓存命中: type={$type}, hash={$hash}, post={$postId}, age=" . (time() - $entry['t']) . 's');
        return $entry['data'];
    }

    /**
     * 写入 AI 响应缓存
     *
     * @param string $type      缓存类型
     * @param string $content   输入内容
     * @param array  $data      AI 响应数据
     * @param string $extraKey  额外标识
     * @param int    $postId    关联文章ID
     */
    public static function set(string $type, string $content, array $data, string $extraKey = '', int $postId = 0): void
    {
        $hash = self::hash($type, $content, $extraKey);
        $cache = self::load();

        $entry = [
            't'    => time(),
            'data' => $data,
            'type' => $type,
        ];

        if ($postId > 0) {
            $entry['post_modified'] = get_post_modified_time('Y-m-d H:i:s', true, $postId) ?: current_time('mysql');
        }

        $cache[$hash] = $entry;
        self::$memoryCache = $cache;

        // 概率性 GC + 限长
        $count = count($cache);
        if ($count > self::MAX_CACHE_SIZE || mt_rand(1, 100) <= (self::GC_PROBABILITY * 100)) {
            self::gc();
        }

        Logger::info('ai_cache', "缓存写入: type={$type}, hash={$hash}, post={$postId}, total_entries=" . count(self::$memoryCache));
    }

    /**
     * 使特定文章的缓存失效
     *
     * @param int    $postId 文章ID
     * @param string $type   缓存类型（空=全部）
     */
    public static function invalidatePost(int $postId, string $type = ''): void
    {
        $cache = self::load();
        $removed = 0;

        foreach ($cache as $hash => $entry) {
            if (!empty($entry['post_modified'])) {
                // 检查是否属于该文章（通过post_modified匹配无法直接判断，需要遍历）
                // 更可靠的方式：文章更新时直接清除
                if ($type && ($entry['type'] ?? '') !== $type) {
                    continue;
                }
                unset($cache[$hash]);
                $removed++;
            }
        }

        if ($removed > 0) {
            self::$memoryCache = $cache;
            Logger::info('ai_cache', "缓存失效: post={$postId}, type=" . ($type ?: 'all') . ", removed={$removed}");
        }
    }

    /**
     * 清空所有缓存
     */
    public static function flush(): void
    {
        delete_option(self::OPTION_KEY);
        self::$memoryCache = [];
        self::$loaded = true;
        Logger::info('ai_cache', '缓存全部清空');
    }

    /**
     * 获取缓存统计
     */
    public static function stats(): array
    {
        $cache = self::load();
        $byType = [];
        $total = count($cache);
        $expired = 0;

        $now = time();
        foreach ($cache as $entry) {
            $type = $entry['type'] ?? 'unknown';
            $byType[$type] = ($byType[$type] ?? 0) + 1;
            $ttl = self::TTL[$type] ?? 3600;
            if ($now - $entry['t'] > $ttl) {
                $expired++;
            }
        }

        return [
            'total'   => $total,
            'expired' => $expired,
            'active'  => $total - $expired,
            'by_type' => $byType,
        ];
    }

    /**
     * 持久化缓存（在 shutdown 时调用）
     */
    public static function persist(): void
    {
        if (self::$memoryCache === null) {
            return;
        }
        update_option(self::OPTION_KEY, self::$memoryCache, false);
        self::$loaded = false; // 下次重新加载
    }

    // ===== 内部 =====

    private static function hash(string $type, string $content, string $extraKey = ''): string
    {
        return $type . '_' . md5($content . '|' . $extraKey);
    }

    private static function load(): array
    {
        if (!self::$loaded) {
            $data = get_option(self::OPTION_KEY, []);
            self::$memoryCache = is_array($data) ? $data : [];
            self::$loaded = true;
        }
        return self::$memoryCache;
    }

    /**
     * 垃圾回收：清除过期条目 + 超过上限时清最旧条目
     */
    private static function gc(): void
    {
        $cache = self::$memoryCache;
        if (empty($cache)) return;

        $now = time();

        // 1. 清除过期条目
        foreach ($cache as $hash => $entry) {
            $ttl = self::TTL[$entry['type'] ?? 'custom'] ?? 3600;
            if ($now - $entry['t'] > $ttl) {
                unset($cache[$hash]);
            }
        }

        // 2. 如果还超限，按时间排序删除最旧的
        if (count($cache) > self::MAX_CACHE_SIZE) {
            uasort($cache, function ($a, $b) {
                return $a['t'] - $b['t'];
            });
            $cache = array_slice($cache, -self::MAX_CACHE_SIZE, null, true);
        }

        self::$memoryCache = $cache;
        Logger::info('ai_cache', 'GC完成: entries=' . count($cache));
    }
}
