package com.fdkankan.scene.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fdkankan.common.constant.CameraTypeEnum;
import com.fdkankan.common.constant.CommonStatus;
import com.fdkankan.common.constant.ErrorCode;
import com.fdkankan.common.constant.TbStatus;
import com.fdkankan.common.exception.BusinessException;
import com.fdkankan.common.util.RandomUtil;
import com.fdkankan.redis.constant.RedisKey;
import com.fdkankan.redis.constant.RedisLockKey;
import com.fdkankan.redis.util.RedisLockUtil;
import com.fdkankan.redis.util.RedisUtil;
import com.fdkankan.scene.entity.Scene3dNum;
import com.fdkankan.scene.mapper.IScene3dNumMapper;
import com.fdkankan.scene.service.IScene3dNumService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;

import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>
 * 八目场景编码表 服务实现类
 * </p>
 *
 * @author dengsixing
 * @since 2021-12-23
 */
@Slf4j
@Service
@RefreshScope
public class Scene3dNumServiceImpl extends ServiceImpl<IScene3dNumMapper, Scene3dNum> implements IScene3dNumService {

    private

    @Autowired
    RedisUtil redisUtil;
    @Autowired
    RedisLockUtil redisLockUtil;
    @Value("${scene.num.threshold:1000}")
    private int threshold;
    @Value("${scene.num.prefix:V4-}")
    private String numPrefix;
    @Value("${scene.num.batchSize:100}")
    private int batchSize;

    @Override
    public String generateSceneNum(Integer cameraType){
        // 从缓存中获取
        String sceneNum = redisUtil.lLeftPop(RedisKey.FDKANKAN_SCENE_NUMS);
        if(Objects.nonNull(sceneNum)){
            return addPrefix(sceneNum,cameraType);
        }
        // 分布式加锁
        Long loading =  redisUtil.incr(RedisLockKey.LOCK_FDKANKAN_SCENE_NUMS, 1);
        if (loading.compareTo(1L) == 0) {
            try {
                log.info("开始从数据库加载场景码");
                List<String> nums = this.findSceneNum();
                this.updateUsedStatus(nums);
                redisUtil.lRightPushAll(RedisKey.FDKANKAN_SCENE_NUMS, nums);
                log.info("场景码加载完成");
            } catch (Exception e) {
                log.error("场景码加载失败", e);
            } finally {
                redisUtil.decr(RedisLockKey.LOCK_FDKANKAN_SCENE_NUMS, 1);
            }
        }else{
            // 等待1秒加载缓存
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        sceneNum = redisUtil.lLeftPop(RedisKey.FDKANKAN_SCENE_NUMS);
        if(StrUtil.isEmpty(sceneNum)){
            log.error("场景码加载失败");
            throw new BusinessException(ErrorCode.FAILURE_CODE_5053);
        }
        return addPrefix(sceneNum,cameraType);
    }

    private  static  String addPrefix( String num,Integer cameraType){
        if(cameraType == null){
            return num;
        }
        return CameraTypeEnum.getSceneNumPrefixByType(cameraType) + num;
    }

    @Override
    public void batchCreateSceneNum() {

        String lockKey = String.format(RedisLockKey.LOCK_BATCH_CREATE_NUM);
        boolean lock = redisLockUtil.lock(lockKey, RedisKey.EXPIRE_TIME_10_MINUTE);
        if(!lock){
            return;
        }
        try {
            long count = this.count(
                new LambdaQueryWrapper<Scene3dNum>().eq(Scene3dNum::getUsed, CommonStatus.NO.code()));
            if(count > threshold){
                return;
            }
            int batchCnt = threshold / batchSize + (threshold % batchSize > 0 ? 1 : 0);
            for (int i = 0; i < batchCnt; i++){
                Set<String> numSet = this.turnCreateSceneNum(batchSize);
                List<Scene3dNum> scene3dNumList = numSet.stream().map(num -> {
                    Scene3dNum scene3dNum = new Scene3dNum();
                    scene3dNum.setNum(num);
                    return scene3dNum;
                }).collect(Collectors.toList());
                this.saveBatch(scene3dNumList);
            }
        }finally {
            redisLockUtil.unlockLua(lockKey);
        }
    }

    private Set<String> turnCreateSceneNum(int cnt){

        String num;
        Set<String> numSet = new HashSet<>();
        for(int i = 0; i < cnt; i++){
            num = numPrefix + RandomUtil.generateShortUuid();
            numSet.add(num);
        }
        List<Scene3dNum> existList = this.list(
            new LambdaQueryWrapper<Scene3dNum>()
                .in(Scene3dNum::getNum, numSet));

        Set<String> existNumSet = null;
        if(CollUtil.isNotEmpty(existList)){
            existNumSet = existList.stream().map(Scene3dNum::getNum).collect(Collectors.toSet());
        }
        if(CollUtil.isNotEmpty(existNumSet)){
            numSet.removeAll(existNumSet);
        }
        if(numSet.size() < cnt){
            numSet.addAll(this.turnCreateSceneNum(cnt - numSet.size()));
        }
        return numSet;
    }





    @Override
    public List<String> findSceneNum() {
        return baseMapper.findSceneNum();
    }

    @Override
    public Scene3dNum findByNum(String sceneCode) {
        List<Scene3dNum> list = this.list(new LambdaQueryWrapper<Scene3dNum>()
                .eq(Scene3dNum::getTbStatus, TbStatus.VALID.code())
                .eq(Scene3dNum::getNum, sceneCode));
        if(CollUtil.isEmpty(list)){
            return null;
        }
        return list.get(0);
    }

    @Override
    public void updateUsedStatus(List<String> nums) {
        baseMapper.updateUsedStatus(nums);
    }

}
