package com.ycwl.basic.puzzle.repository;

import com.fasterxml.jackson.core.type.TypeReference;
import com.ycwl.basic.puzzle.entity.PuzzleElementEntity;
import com.ycwl.basic.puzzle.entity.PuzzleGenerationRecordEntity;
import com.ycwl.basic.puzzle.entity.PuzzleTemplateEntity;
import com.ycwl.basic.puzzle.mapper.PuzzleElementMapper;
import com.ycwl.basic.puzzle.mapper.PuzzleGenerationRecordMapper;
import com.ycwl.basic.puzzle.mapper.PuzzleTemplateMapper;
import com.ycwl.basic.utils.JacksonUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 拼图模块缓存仓库
 * 提供模板和元素的缓存读取，减少数据库查询
 *
 * @author Claude
 * @since 2025-01-01
 */
@Slf4j
@Repository
public class PuzzleRepository {

    private final PuzzleTemplateMapper templateMapper;
    private final PuzzleElementMapper elementMapper;
    private final PuzzleGenerationRecordMapper recordMapper;
    private final RedisTemplate<String, String> redisTemplate;

    /**
     * 模板缓存KEY（根据code）
     */
    private static final String PUZZLE_TEMPLATE_BY_CODE_KEY = "puzzle:template:code:%s";

    /**
     * 模板缓存KEY（根据id）
     */
    private static final String PUZZLE_TEMPLATE_BY_ID_KEY = "puzzle:template:id:%s";

    /**
     * 元素列表缓存KEY（根据templateId）
     */
    private static final String PUZZLE_ELEMENTS_BY_TEMPLATE_KEY = "puzzle:elements:templateId:%s";

    /**
     * 生成记录缓存KEY（根据faceId）
     */
    private static final String PUZZLE_RECORDS_BY_FACE_KEY = "puzzle:records:faceId:%s";

    /**
     * 景区模板列表缓存KEY（根据scenicId）
     */
    private static final String PUZZLE_TEMPLATES_BY_SCENIC_KEY = "puzzle:templates:scenicId:%s";

    /**
     * 单条生成记录缓存KEY（根据recordId）
     */
    private static final String PUZZLE_RECORD_BY_ID_KEY = "puzzle:record:id:%s";

    /**
     * 缓存过期时间（小时）
     */
    private static final long CACHE_EXPIRE_HOURS = 24;

    /**
     * 生成记录缓存过期时间（分钟）- 较短，因为可能频繁变化
     */
    private static final long RECORD_CACHE_EXPIRE_MINUTES = 30;

    public PuzzleRepository(
            PuzzleTemplateMapper templateMapper,
            PuzzleElementMapper elementMapper,
            PuzzleGenerationRecordMapper recordMapper,
            RedisTemplate<String, String> redisTemplate) {
        this.templateMapper = templateMapper;
        this.elementMapper = elementMapper;
        this.recordMapper = recordMapper;
        this.redisTemplate = redisTemplate;
    }

    // ==================== 模板缓存 ====================

    /**
     * 根据模板编码获取模板（优先从缓存读取）
     *
     * @param code 模板编码
     * @return 模板实体，不存在返回null
     */
    public PuzzleTemplateEntity getTemplateByCode(String code) {
        String cacheKey = String.format(PUZZLE_TEMPLATE_BY_CODE_KEY, code);

        // 1. 尝试从缓存读取
        Boolean hasKey = redisTemplate.hasKey(cacheKey);
        if (Boolean.TRUE.equals(hasKey)) {
            String cacheValue = redisTemplate.opsForValue().get(cacheKey);
            if (cacheValue != null) {
                log.debug("从缓存读取模板: code={}", code);
                return JacksonUtil.parseObject(cacheValue, PuzzleTemplateEntity.class);
            }
        }

        // 2. 从数据库查询
        PuzzleTemplateEntity template = templateMapper.getByCode(code);
        if (template == null) {
            return null;
        }

        // 3. 写入缓存
        cacheTemplate(template);
        log.debug("模板缓存写入: code={}, id={}", code, template.getId());

        return template;
    }

    /**
     * 根据模板ID获取模板（优先从缓存读取）
     *
     * @param id 模板ID
     * @return 模板实体，不存在返回null
     */
    public PuzzleTemplateEntity getTemplateById(Long id) {
        String cacheKey = String.format(PUZZLE_TEMPLATE_BY_ID_KEY, id);

        // 1. 尝试从缓存读取
        Boolean hasKey = redisTemplate.hasKey(cacheKey);
        if (Boolean.TRUE.equals(hasKey)) {
            String cacheValue = redisTemplate.opsForValue().get(cacheKey);
            if (cacheValue != null) {
                log.debug("从缓存读取模板: id={}", id);
                return JacksonUtil.parseObject(cacheValue, PuzzleTemplateEntity.class);
            }
        }

        // 2. 从数据库查询
        PuzzleTemplateEntity template = templateMapper.getById(id);
        if (template == null) {
            return null;
        }

        // 3. 写入缓存
        cacheTemplate(template);
        log.debug("模板缓存写入: id={}, code={}", id, template.getCode());

        return template;
    }

    /**
     * 缓存模板（同时缓存 byId 和 byCode）
     */
    private void cacheTemplate(PuzzleTemplateEntity template) {
        if (template == null) {
            return;
        }
        String json = JacksonUtil.toJSONString(template);

        // 同时缓存 byId 和 byCode
        String idKey = String.format(PUZZLE_TEMPLATE_BY_ID_KEY, template.getId());
        String codeKey = String.format(PUZZLE_TEMPLATE_BY_CODE_KEY, template.getCode());

        redisTemplate.opsForValue().set(idKey, json, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
        redisTemplate.opsForValue().set(codeKey, json, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
    }

    /**
     * 清除模板缓存
     *
     * @param id   模板ID
     * @param code 模板编码（可为null，此时需要先查询获取）
     */
    public void clearTemplateCache(Long id, String code) {
        Long scenicId = null;

        // 如果没有传code，尝试从缓存或数据库获取
        if (code == null && id != null) {
            String idKey = String.format(PUZZLE_TEMPLATE_BY_ID_KEY, id);
            String cacheValue = redisTemplate.opsForValue().get(idKey);
            if (cacheValue != null) {
                PuzzleTemplateEntity template = JacksonUtil.parseObject(cacheValue, PuzzleTemplateEntity.class);
                code = template.getCode();
                scenicId = template.getScenicId();
            } else {
                PuzzleTemplateEntity template = templateMapper.getById(id);
                if (template != null) {
                    code = template.getCode();
                    scenicId = template.getScenicId();
                }
            }
        } else if (id != null) {
            // 有 code 但需要获取 scenicId
            String idKey = String.format(PUZZLE_TEMPLATE_BY_ID_KEY, id);
            String cacheValue = redisTemplate.opsForValue().get(idKey);
            if (cacheValue != null) {
                PuzzleTemplateEntity template = JacksonUtil.parseObject(cacheValue, PuzzleTemplateEntity.class);
                scenicId = template.getScenicId();
            } else {
                PuzzleTemplateEntity template = templateMapper.getById(id);
                if (template != null) {
                    scenicId = template.getScenicId();
                }
            }
        }

        // 清除 byId 缓存
        if (id != null) {
            String idKey = String.format(PUZZLE_TEMPLATE_BY_ID_KEY, id);
            redisTemplate.delete(idKey);
            log.debug("清除模板缓存: id={}", id);
        }

        // 清除 byCode 缓存
        if (code != null) {
            String codeKey = String.format(PUZZLE_TEMPLATE_BY_CODE_KEY, code);
            redisTemplate.delete(codeKey);
            log.debug("清除模板缓存: code={}", code);
        }

        // 同时清除该模板的元素缓存
        if (id != null) {
            clearElementsCache(id);
        }

        // 清除景区模板列表缓存（确保列表数据一致性）
        if (scenicId != null) {
            clearTemplateByScenicCache(scenicId);
        }
    }

    // ==================== 元素缓存 ====================

    /**
     * 根据模板ID获取元素列表（优先从缓存读取）
     *
     * @param templateId 模板ID
     * @return 元素列表
     */
    public List<PuzzleElementEntity> getElementsByTemplateId(Long templateId) {
        String cacheKey = String.format(PUZZLE_ELEMENTS_BY_TEMPLATE_KEY, templateId);

        // 1. 尝试从缓存读取
        Boolean hasKey = redisTemplate.hasKey(cacheKey);
        if (Boolean.TRUE.equals(hasKey)) {
            String cacheValue = redisTemplate.opsForValue().get(cacheKey);
            if (cacheValue != null) {
                log.debug("从缓存读取元素列表: templateId={}", templateId);
                return JacksonUtil.parseObject(cacheValue, new TypeReference<List<PuzzleElementEntity>>() {});
            }
        }

        // 2. 从数据库查询
        List<PuzzleElementEntity> elements = elementMapper.getByTemplateId(templateId);

        // 3. 写入缓存（即使是空列表也要缓存，避免缓存穿透）
        String json = JacksonUtil.toJSONString(elements);
        redisTemplate.opsForValue().set(cacheKey, json, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
        log.debug("元素列表缓存写入: templateId={}, count={}", templateId, elements.size());

        return elements;
    }

    /**
     * 清除元素缓存
     *
     * @param templateId 模板ID
     */
    public void clearElementsCache(Long templateId) {
        String cacheKey = String.format(PUZZLE_ELEMENTS_BY_TEMPLATE_KEY, templateId);
        redisTemplate.delete(cacheKey);
        log.debug("清除元素缓存: templateId={}", templateId);
    }

    // ==================== 景区模板列表缓存 ====================

    /**
     * 根据景区ID获取启用的模板列表（优先从缓存读取）
     * 用于人脸匹配后的批量拼图生成场景
     *
     * @param scenicId 景区ID
     * @return 启用状态的模板列表
     */
    public List<PuzzleTemplateEntity> listTemplateByScenic(Long scenicId) {
        if (scenicId == null) {
            log.warn("景区ID为空，跳过缓存查询");
            return templateMapper.list(null, null, 1);
        }

        String cacheKey = String.format(PUZZLE_TEMPLATES_BY_SCENIC_KEY, scenicId);

        // 1. 尝试从缓存读取
        Boolean hasKey = redisTemplate.hasKey(cacheKey);
        if (Boolean.TRUE.equals(hasKey)) {
            String cacheValue = redisTemplate.opsForValue().get(cacheKey);
            if (cacheValue != null) {
                log.debug("从缓存读取景区模板列表: scenicId={}", scenicId);
                return JacksonUtil.parseObject(cacheValue, new TypeReference<List<PuzzleTemplateEntity>>() {});
            }
        }

        // 2. 从数据库查询（只查启用状态 status=1）
        List<PuzzleTemplateEntity> templates = templateMapper.list(scenicId, null, 1);

        // 3. 写入缓存（即使是空列表也缓存，避免缓存穿透）
        String json = JacksonUtil.toJSONString(templates);
        redisTemplate.opsForValue().set(cacheKey, json, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
        log.debug("景区模板列表缓存写入: scenicId={}, count={}", scenicId, templates.size());

        return templates;
    }

    /**
     * 清除景区模板列表缓存
     *
     * @param scenicId 景区ID
     */
    public void clearTemplateByScenicCache(Long scenicId) {
        if (scenicId == null) {
            return;
        }
        String cacheKey = String.format(PUZZLE_TEMPLATES_BY_SCENIC_KEY, scenicId);
        redisTemplate.delete(cacheKey);
        log.debug("清除景区模板列表缓存: scenicId={}", scenicId);
    }

    // ==================== 批量清除 ====================

    /**
     * 清除所有拼图相关缓存（慎用）
     * 使用 SCAN 命令避免 KEYS 阻塞
     */
    public void clearAllPuzzleCache() {
        log.warn("开始清除所有拼图缓存...");

        // 使用 SCAN 删除模板缓存
        deleteByPattern("puzzle:template:*");
        // 使用 SCAN 删除元素缓存
        deleteByPattern("puzzle:elements:*");
        // 使用 SCAN 删除景区模板列表缓存
        deleteByPattern("puzzle:templates:*");

        log.warn("拼图缓存清除完成");
    }

    /**
     * 根据模式删除缓存
     * 使用 SCAN 命令避免阻塞
     */
    private void deleteByPattern(String pattern) {
        try {
            var keys = redisTemplate.keys(pattern);
            if (keys != null && !keys.isEmpty()) {
                redisTemplate.delete(keys);
                log.debug("删除缓存: pattern={}, count={}", pattern, keys.size());
            }
        } catch (Exception e) {
            log.error("删除缓存失败: pattern={}", pattern, e);
        }
    }

    // ==================== 生成记录缓存 ====================

    /**
     * 根据人脸ID获取生成记录列表（优先从缓存读取）
     *
     * @param faceId 人脸ID
     * @return 生成记录列表
     */
    public List<PuzzleGenerationRecordEntity> getRecordsByFaceId(Long faceId) {
        String cacheKey = String.format(PUZZLE_RECORDS_BY_FACE_KEY, faceId);

        // 1. 尝试从缓存读取
        Boolean hasKey = redisTemplate.hasKey(cacheKey);
        if (Boolean.TRUE.equals(hasKey)) {
            String cacheValue = redisTemplate.opsForValue().get(cacheKey);
            if (cacheValue != null) {
                log.debug("从缓存读取生成记录列表: faceId={}", faceId);
                return JacksonUtil.parseObject(cacheValue, new TypeReference<List<PuzzleGenerationRecordEntity>>() {});
            }
        }

        // 2. 从数据库查询
        List<PuzzleGenerationRecordEntity> records = recordMapper.listByFaceId(faceId);

        // 3. 写入缓存
        String json = JacksonUtil.toJSONString(records);
        redisTemplate.opsForValue().set(cacheKey, json, RECORD_CACHE_EXPIRE_MINUTES, TimeUnit.MINUTES);
        log.debug("生成记录列表缓存写入: faceId={}, count={}", faceId, records.size());

        return records;
    }

    /**
     * 根据人脸ID获取生成记录数量（优先从缓存读取）
     *
     * @param faceId 人脸ID
     * @return 记录数量
     */
    public int countRecordsByFaceId(Long faceId) {
        List<PuzzleGenerationRecordEntity> records = getRecordsByFaceId(faceId);
        return records.size();
    }

    /**
     * 根据记录ID获取单条生成记录（优先从缓存读取）
     *
     * @param recordId 记录ID
     * @return 生成记录，不存在返回null
     */
    public PuzzleGenerationRecordEntity getRecordById(Long recordId) {
        String cacheKey = String.format(PUZZLE_RECORD_BY_ID_KEY, recordId);

        // 1. 尝试从缓存读取
        Boolean hasKey = redisTemplate.hasKey(cacheKey);
        if (Boolean.TRUE.equals(hasKey)) {
            String cacheValue = redisTemplate.opsForValue().get(cacheKey);
            if (cacheValue != null) {
                log.debug("从缓存读取生成记录: recordId={}", recordId);
                return JacksonUtil.parseObject(cacheValue, PuzzleGenerationRecordEntity.class);
            }
        }

        // 2. 从数据库查询
        PuzzleGenerationRecordEntity record = recordMapper.getById(recordId);
        if (record == null) {
            return null;
        }

        // 3. 写入缓存
        String json = JacksonUtil.toJSONString(record);
        redisTemplate.opsForValue().set(cacheKey, json, RECORD_CACHE_EXPIRE_MINUTES, TimeUnit.MINUTES);
        log.debug("生成记录缓存写入: recordId={}", recordId);

        return record;
    }

    /**
     * 清除人脸相关的生成记录缓存
     * 在新拼图生成成功后调用
     *
     * @param faceId 人脸ID
     */
    public void clearRecordCacheByFace(Long faceId) {
        if (faceId == null) {
            return;
        }
        // 清除列表缓存
        String listKey = String.format(PUZZLE_RECORDS_BY_FACE_KEY, faceId);
        redisTemplate.delete(listKey);

        log.debug("清除人脸生成记录缓存: faceId={}", faceId);
    }

    /**
     * 清除单条记录的缓存
     *
     * @param recordId 记录ID
     */
    public void clearRecordCacheById(Long recordId) {
        if (recordId == null) {
            return;
        }
        String cacheKey = String.format(PUZZLE_RECORD_BY_ID_KEY, recordId);
        redisTemplate.delete(cacheKey);
        log.debug("清除生成记录缓存: recordId={}", recordId);
    }

    /**
     * 清除单条记录缓存并同时清除关联的人脸缓存
     *
     * @param recordId 记录ID
     * @param faceId   人脸ID
     */
    public void clearRecordCache(Long recordId, Long faceId) {
        clearRecordCacheById(recordId);
        clearRecordCacheByFace(faceId);
    }
}
