package cn.iocoder.yudao.module.bpm.service.processmapping;

import cn.hutool.core.bean.BeanUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.cache.CacheUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.processmapping.vo.BpmProcessMappingConfigCreateReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.processmapping.vo.BpmProcessMappingConfigPageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.processmapping.vo.BpmProcessMappingConfigUpdateReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.processmapping.BpmProcessMappingConfigDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.processmapping.BpmProcessMappingConfigMapper;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.time.Duration;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_MAPPING_CONFIG_NOT_EXISTS;

/**
 * 流程映射配置 Service 实现类
 * 
 * 使用二级缓存架构：
 * 1. 一级缓存（内存）：Google Guava LoadingCache，10分钟过期
 * 2. 二级缓存（Redis）：Spring Cache，1小时过期
 * 3. 数据库：MySQL
 *
 * @author 系统
 */
@Service
@Validated
@Slf4j
public class ProcessMappingConfigServiceImpl implements ProcessMappingConfigService {

    private static final String CACHE_KEY_PREFIX = "process_mapping_config";

    @Resource
    private BpmProcessMappingConfigMapper processMappingConfigMapper;

    /**
     * 一级缓存（内存）- Google Guava LoadingCache
     * 缓存key为processCode，缓存时间10分钟
     */
    @Getter
    private final LoadingCache<String, BpmProcessMappingConfigDO> localCache = CacheUtils.buildAsyncReloadingCache(
            Duration.ofMinutes(10L),
            new CacheLoader<String, BpmProcessMappingConfigDO>() {
                @Override
                public BpmProcessMappingConfigDO load(String processCode) {
                    log.info("[load][一级缓存miss，从二级缓存加载] processCode={}", processCode);
                    // 从二级缓存（Redis）加载
                    BpmProcessMappingConfigDO config = getFromRedisCache(processCode);
                    return config != null ? config : new BpmProcessMappingConfigDO(); // 避免缓存穿透
                }
            });

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createProcessMappingConfig(BpmProcessMappingConfigCreateReqVO createReqVO) {
        // 校验processCode唯一性
        validateProcessCodeUnique(null, createReqVO.getProcessCode());
        
        // 插入
        BpmProcessMappingConfigDO config = BeanUtil.toBean(createReqVO, BpmProcessMappingConfigDO.class);
        processMappingConfigMapper.insert(config);
        
        // 清除缓存
        evictCache(createReqVO.getProcessCode());
        
        return config.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateProcessMappingConfig(BpmProcessMappingConfigUpdateReqVO updateReqVO) {
        // 校验存在
        BpmProcessMappingConfigDO oldConfig = validateProcessMappingConfigExists(updateReqVO.getId());
        
        // 校验processCode唯一性
        if (!oldConfig.getProcessCode().equals(updateReqVO.getProcessCode())) {
            validateProcessCodeUnique(updateReqVO.getId(), updateReqVO.getProcessCode());
        }
        
        // 更新
        BpmProcessMappingConfigDO updateObj = BeanUtil.toBean(updateReqVO, BpmProcessMappingConfigDO.class);
        processMappingConfigMapper.updateById(updateObj);
        
        // 清除缓存
        evictCache(oldConfig.getProcessCode());
        if (!oldConfig.getProcessCode().equals(updateReqVO.getProcessCode())) {
            evictCache(updateReqVO.getProcessCode());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteProcessMappingConfig(Long id) {
        // 校验存在
        BpmProcessMappingConfigDO config = validateProcessMappingConfigExists(id);
        
        // 删除
        processMappingConfigMapper.deleteById(id);
        
        // 清除缓存
        evictCache(config.getProcessCode());
    }

    @Override
    public BpmProcessMappingConfigDO getProcessMappingConfig(Long id) {
        return processMappingConfigMapper.selectById(id);
    }

    @Override
    public BpmProcessMappingConfigDO getProcessMappingConfigByCode(String processCode) {
        try {
            // 先从一级缓存（内存）获取
            BpmProcessMappingConfigDO config = localCache.get(processCode);
            if (config != null && config.getId() != null) {
                log.debug("[getProcessMappingConfigByCode][一级缓存命中] processCode={}", processCode);
                return config;
            }
            return null;
        } catch (Exception e) {
            log.error("[getProcessMappingConfigByCode][缓存异常] processCode={}", processCode, e);
            // 缓存异常时直接查询数据库
            return processMappingConfigMapper.selectByProcessCode(processCode);
        }
    }

    /**
     * 从二级缓存（Redis）获取配置
     * 使用Spring Cache注解，自动管理Redis缓存
     */
    @Cacheable(value = CACHE_KEY_PREFIX + "#1h", key = "#processCode", unless = "#result == null")
    public BpmProcessMappingConfigDO getFromRedisCache(String processCode) {
        log.info("[getFromRedisCache][二级缓存miss，从数据库加载] processCode={}", processCode);
        // 从数据库查询
        return processMappingConfigMapper.selectByProcessCode(processCode);
    }

    @Override
    public PageResult<BpmProcessMappingConfigDO> getProcessMappingConfigPage(BpmProcessMappingConfigPageReqVO pageReqVO) {
        return processMappingConfigMapper.selectPage(pageReqVO);
    }

    @Override
    public void refreshCache() {
        // 清空一级缓存
        localCache.invalidateAll();
        log.info("[refreshCache][已清空一级缓存]");
        
        // 清空二级缓存（通过Spring Cache管理）
        // 注意：这里需要具体实现清空所有Redis缓存的逻辑
    }

    /**
     * 清除指定processCode的缓存
     */
    @CacheEvict(value = CACHE_KEY_PREFIX + "#1h", key = "#processCode")
    public void evictCache(String processCode) {
        // 清除一级缓存
        localCache.invalidate(processCode);
        log.info("[evictCache][已清除缓存] processCode={}", processCode);
        // 清除二级缓存由@CacheEvict注解自动处理
    }

    private void validateProcessCodeUnique(Long id, String processCode) {
        BpmProcessMappingConfigDO config = processMappingConfigMapper.selectByProcessCode(processCode);
        if (config == null) {
            return;
        }
        // 如果 id 为空，说明不用比较是否为相同 id 的配置
        if (id == null) {
            throw exception(PROCESS_MAPPING_CONFIG_NOT_EXISTS);
        }
        if (!config.getId().equals(id)) {
            throw exception(PROCESS_MAPPING_CONFIG_NOT_EXISTS);
        }
    }

    private BpmProcessMappingConfigDO validateProcessMappingConfigExists(Long id) {
        if (id == null) {
            return null;
        }
        BpmProcessMappingConfigDO config = processMappingConfigMapper.selectById(id);
        if (config == null) {
            throw exception(PROCESS_MAPPING_CONFIG_NOT_EXISTS);
        }
        return config;
    }

} 