package com.xxfc.platform.vehicle.biz;

import com.alibaba.fastjson.JSON;
import com.github.wxiaoqi.security.common.biz.BaseBiz;
import com.github.wxiaoqi.security.common.exception.BaseException;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.xxfc.platform.vehicle.constant.ConstantType;
import com.xxfc.platform.vehicle.constant.RedisKey;
import com.xxfc.platform.vehicle.entity.Constant;
import com.xxfc.platform.vehicle.mapper.ConstantMapper;
import com.xxfc.platform.vehicle.pojo.ConstantVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskExecutor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

@Service
@Slf4j
public class ConstantBiz extends BaseBiz<ConstantMapper,Constant> {

    @Autowired
    private RedisTemplate customRedisTemplate;
    @Autowired
    private TaskExecutor customTaskExecutor;
    /**
     * 每批次最大更新常量条目数
     */
    public static final Integer MAX_BATCH_SIZE_CONSTANT_UPDATE = 1000;

    /**
     * 转换实体类为VO
     * @param constant
     * @return
     */
    private ConstantVo constant2VO(Constant constant){
        ConstantVo constantVo = new ConstantVo();
        constantVo.setType(constant.getType());
        constantVo.setCode(constant.getCode());
        constantVo.setVal(constant.getVal());
        return constantVo;
    }

    /**
     * 获取对应redis中的hash的key
     * @param type
     * @return
     */
    private String getConstantRedisKey(Integer type){
        return RedisKey.CONSTANT_CACHE_PREFIX +type;
    }

    /**
     * 查询所有类型下的常量数据，先从缓存读
     * @param type
     * @return
     */
    public List<ConstantVo> getAllConstantByType(Integer type) {
        String cacheConstantsJsonStr = String.valueOf(customRedisTemplate.opsForValue().get(getConstantRedisKey(type)));
        Map<String,Object> constantMap = new HashMap<>();
        if(StringUtils.isNotBlank(cacheConstantsJsonStr)){
            constantMap = JSON.parseObject(cacheConstantsJsonStr);
        }
        List<ConstantVo> constantVoList = Lists.newArrayList();
        //尝试从缓存中查询
        if(MapUtils.isNotEmpty(constantMap)){
            for(Map.Entry<String,Object> constantEntry:constantMap.entrySet()){
                ConstantVo constant = new ConstantVo();
                constant.setType(type);
                constant.setCode(Integer.valueOf(constantEntry.getKey()));
                constant.setVal(String.valueOf(constantEntry.getValue()));
                constantVoList.add(constant);
            }
            return constantVoList;
        }
        //刷新缓存
        refreshCacheAsync();
        //缓存中不存在则从bd中查询
        return getAllConstantByTypeNoCache(type);
    }

    /**
     * 获取db中所有类型下的常量数据
     * @param type
     * @return
     */
    public List<ConstantVo> getAllConstantByTypeNoCache(Integer type) {
        List<ConstantVo> constantVoList = Lists.newArrayList();
        Constant param = new Constant();
        param.setType(type);
        List<Constant> constantListFromDb= mapper.select(param);
        if(CollectionUtils.isNotEmpty(constantListFromDb)){
            for(Constant constant:constantListFromDb){
                constantVoList.add(constant2VO(constant));
            }
        }
        return constantVoList;
    }

    public void refreshCacheAsync(){
        customTaskExecutor.execute(new Runnable() {
            @Override
            public void run() {
                refreshCache();
            }
        });
    }

    /**
     * 5分钟内刷新数据到缓存
     */
    @Scheduled(cron = "0 */5 * * * *")//每5分钟刷新一次数据
    public void refreshCache(){
        log.info("刷新常量数据任务开始");
        List<Integer> types = mapper.getAllConstantTypes();
        for(Integer type : types){
            log.info("刷新类型【"+type+"】常量数据任务开始");
            //redis方式实现乐观锁
            String redisLockKey = RedisKey.CONSTANT_REFRESH_LOCK_PREFIX +type+":"+(DateTime.now().getMinuteOfDay()/5);//同一日每5分钟只刷新一次
            Boolean suc = customRedisTemplate.opsForValue().setIfAbsent(redisLockKey, String.valueOf(DateTime.now().getMillis()));
            if(!suc){
                continue;
            }
            customRedisTemplate.expire(redisLockKey,5, TimeUnit.MINUTES);//5分钟内过期
            List<ConstantVo> constantVoList = getAllConstantByTypeNoCache(type);
            if(CollectionUtils.isNotEmpty(constantVoList)){
                Map<String,String> constantMap = new HashMap<>();
                for(ConstantVo constantVo:constantVoList){
                    constantMap.put(String.valueOf(constantVo.getCode()),constantVo.getVal());
                }
                customRedisTemplate.opsForValue().set(getConstantRedisKey(type), JSON.toJSONString(constantMap));
            }
            log.info("刷新类型【"+type+"】常量数据任务完成");
        }
        log.info("刷新常量数据任务成功");
    }

    /**
     * 检查批量操作批次大小是否合法
     * @param constants
     * @return
     */
    private Boolean checkBatchSize(List<Constant> constants){

        if(CollectionUtils.isEmpty(constants)){
            return Boolean.FALSE;
        }
        if(constants.size()>MAX_BATCH_SIZE_CONSTANT_UPDATE){
            throw new BaseException(" exceed max batch size");
        }

        return Boolean.TRUE;
    }

    /**
     * 批量更新常量
     * @param type
     * @param constants
     */
    @Transactional
    public Integer updateConstants(Integer type, List<Constant> constants) {
        if(!checkBatchSize(constants)){
            return 0;
        }
        Map<Integer,String> codeAndVal = Maps.newHashMap();

        for(Constant constant:constants){
            constant.setType(type);
            Integer affected = mapper.updateByTypeAndCode(constant);
            if(affected>0){
                codeAndVal.put(constant.getCode(),constant.getVal());
            }
        }
        return codeAndVal.size();
    }

    /**
     * 删除常量
     * @param type
     * @param codes
     */
    @Transactional
    public Integer delConstant(Integer type, List<Integer> codes){
        if(codes ==null||codes.size()==0){
            return 0;
        }
        if(codes.size()>MAX_BATCH_SIZE_CONSTANT_UPDATE){
            throw new BaseException(" exceed max batch size");
        }
        List<String> delCodes = Lists.newArrayList();
        for(Integer code:codes){
            Constant param = new Constant();
            param.setType(type);
            param.setCode(code);
            Integer affected = mapper.delByTypeAndCode(param);
            if(affected>0){
                delCodes.add(String.valueOf(code));
            }
        }
        return delCodes.size();
    }

    /**
     * 增加常量
     * @param type
     * @param constants
     */
    public Integer addConstants(Integer type, List<Constant> constants){
        if(!checkBatchSize(constants)){
            return 0;
        }
        Map<String,String> codeAndVal = Maps.newHashMap();
        for(Constant constant:constants){
            constant.setType(type);
            Integer affected = mapper.insertIgnoreOnDuplicate(constant);
            if(affected>0){
                codeAndVal.put(constant.getCode().toString(),constant.getVal());
            }
        }
        return codeAndVal.size();
    }


    /**
     * 检查常量是否已存在
     * @param type
     * @param code
     * @return
     */
    public Boolean checkIfExists(Integer type,Integer code){
        if(!ConstantType.exists(type)){
            throw new BaseException(" no such type of constant");
        }

        List<ConstantVo> constantVoList =  getAllConstantByType(type);
        if(CollectionUtils.isEmpty(constantVoList)){
            throw new BaseException(" no such code of constant in relative type");
        }

        for(ConstantVo constantVo:constantVoList){
            if(NumberUtils.compare(code,constantVo.getCode())==0){
                return Boolean.TRUE;
            }
        }
        throw new BaseException(" no such code of constant in relative type");
    }

}
