package edu.whut.smilepicturebackend.manager.cache;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.core.type.TypeReference;

import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;


@Service
@RequiredArgsConstructor
public class MyCacheManager {
    private final ObjectMapper mapper;
    /**
     * 本地缓存
     */
    private final Cache<String, String> localCache = Caffeine.newBuilder()
            .initialCapacity(1024)
            .maximumSize(10_000L)  // 最大 10000 条
            .expireAfterWrite(Duration.ofMinutes(5))  // 缓存 5 分钟后移除
            .build();

    /**
     * 分布式缓存
     */
    private final StringRedisTemplate stringRedisTemplate;

    public <T> T getFromCacheOrDatabase(
            String cacheKey,
            TypeReference<T> typeRef,
            Supplier<T> dbSupplier,
            int redisExpireSeconds) {

        // 1) 本地缓存
        String cachedValue = localCache.getIfPresent(cacheKey);
        if (cachedValue != null) {
            try {
                return mapper.readValue(cachedValue, typeRef);
            } catch (Exception ignore) {}
        }

        // 2) Redis
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        cachedValue = ops.get(cacheKey);
        if (cachedValue != null) {
            localCache.put(cacheKey, cachedValue);
            try {
                return mapper.readValue(cachedValue, typeRef);
            } catch (Exception ignore) {}
        }

        // 3) DB 回源
        T dbValue = dbSupplier.get();

        // ===== 空值缓存防穿透 =====
        if (isEmptyValue(dbValue)) {
            String emptyJson = "{}";
            ops.set(cacheKey, emptyJson, 60, TimeUnit.SECONDS); // 缓存60秒
            localCache.put(cacheKey, emptyJson);
            return dbValue;
        }
        try {
            String json = mapper.writeValueAsString(dbValue);
            ops.set(cacheKey, json, redisExpireSeconds, TimeUnit.SECONDS);
            localCache.put(cacheKey, json);
        } catch (Exception ignore) {}
        return dbValue;
    }

    /**
     * 判断值是否为空，用于空值缓存
     */
    private boolean isEmptyValue(Object value) {
        if (value == null) {
            return true;
        }
        if (value instanceof Collection) {
            return ((Collection<?>) value).isEmpty();
        }
        if (value instanceof Map) {
            return ((Map<?, ?>) value).isEmpty();
        }
        // 这里可以根据你的项目补充 Page 判空逻辑
        if (value instanceof com.baomidou.mybatisplus.extension.plugins.pagination.Page) {
            return ((com.baomidou.mybatisplus.extension.plugins.pagination.Page<?>) value).getRecords().isEmpty();
        }
        return false;
    }

    /**
     * 删除缓存
     * @param spaceId
     */
    public void clearCacheBySpaceId(Long spaceId) {
        // 构造命名空间，公共图库使用 "public"，其他空间使用 spaceId
        String namespace = (spaceId == null) ? "public" : String.valueOf(spaceId);

        // Redis SCAN 模式匹配的前缀
        String pattern = "smilepicture:listPictureVOByPage:spaceId:" + namespace + ":*";

        // 使用 SCAN 命令进行遍历，查找所有相关的缓存 key
        ScanOptions options = ScanOptions.scanOptions().match(pattern).count(100).build();
        Cursor<byte[]> cursor = stringRedisTemplate.execute(
                (redisConnection) -> redisConnection.scan(options),
                true
        );

        // 删除匹配的缓存
        while (cursor.hasNext()) {
            byte[] rawKey = cursor.next();
            String cacheKey = new String(rawKey, StandardCharsets.UTF_8);  // 转换 byte[] 为 String
            stringRedisTemplate.delete(cacheKey);  // 删除 Redis 缓存
        }

        // 删除本地缓存 Caffeine
        // 由于 Caffeine 不支持像 Redis 那样通过模式匹配查询所有 key，我们依然按分页模式来清除
        for (int current = 1; current <= 100; current++) {  // 假设最多查询100页
            for (int size = 10; size <= 100; size += 10) {
                String cacheKey = "smilepicture:listPictureVOByPage:spaceId:" + namespace + ":current:" + current + ":size:" + size;
                localCache.invalidate(cacheKey);  // 清除本地缓存
            }
        }
    }
}
