package com.xinxincaravan.caravan.vehicle.biz;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.wxiaoqi.security.common.biz.BaseBiz;
import com.google.common.collect.Lists;
import com.xinxincaravan.caravan.vehicle.common.CustomIllegalParamException;
import com.xinxincaravan.caravan.vehicle.constant.RedisKey;
import com.xinxincaravan.caravan.vehicle.constant.RegionType;
import com.xinxincaravan.caravan.vehicle.entity.SysRegion;
import com.xinxincaravan.caravan.vehicle.mapper.SysRegionMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
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 java.util.List;

@Service
@Slf4j
public class SysRegionBiz extends BaseBiz<SysRegionMapper, SysRegion> {

    @Autowired
    private RedisTemplate customRedisTemplate;//todo redis需要改为多实例

    @Autowired
    private TaskExecutor customTaskExecutor;
    /**
     * 国家没有父地区，其parentId为0
     */
    private static final Long PARENT_ID_NONE = 0l;



    public List<SysRegion> getRegionsByCodes(List<Long> ids){
        List<String> redisCacheKeys = Lists.newArrayList();
        if(CollectionUtils.isEmpty(ids)){
            throw new CustomIllegalParamException("empty id list");
        }
        for(Long id:ids){
            redisCacheKeys.add(RedisKey.SYS_REGION_CACHE_PREFIX+id);
        }
        List<String> cachedRegionStrList = customRedisTemplate.opsForValue().multiGet(redisCacheKeys);
        List<SysRegion> rs = Lists.newArrayList();
        Boolean hasCache = Boolean.TRUE;
        for(String cachedRegionStr:cachedRegionStrList){
            if(StringUtils.isBlank(cachedRegionStr)){
                hasCache = Boolean.FALSE;
                continue;
            }else{
                rs.add(JSONObject.parseObject(cachedRegionStr,SysRegion.class));
            }

        }
        if(!hasCache){//从db读
            rs = mapper.getByIdList(ids);
        }
        return rs;
    }


    //获取相应地区类型对应在id的位数,作为对应parent查找其下地区的redis key组成部分
    private String getPrefixOfAgencyId(Integer type,Long parentId){
        switch(type){
            case -1:
                return String.valueOf(-1);
            case 0:
                return String.valueOf(0);
            case 1:
                return String.valueOf(parentId).substring(0,2);
            case 2:
                return String.valueOf(parentId).substring(0,4);
            default:
                throw new CustomIllegalParamException("not valid region");
        }

    }

    //获取相应地区类型对应在id的位数,作为对应parent查找其下地区的redis key组成部分
    private String getPrefixOfAgencyId(Long parentId){
        Integer type = getTypeFromId(parentId);
        return getPrefixOfAgencyId(type,parentId);
    }

    /**
     * 根据id获得所属类型
     * @param id
     * @return
     */
    private Integer getTypeFromId(Long id){
        if(id == 0){
            return RegionType.ALL.getCode();
        }if(id < 10){
            return RegionType.COUNTRY.getCode();
        }else if(id%100>0){//第1、2非零，即3级地区
            return RegionType.TOWN.getCode();
        }else if(id%10000>0){//第1、2为零，第3、4位非0，即2级地区
            return RegionType.CITY.getCode();
        }else{//第1、2、3、4为零，第5、6位非0，即2级地区
            return RegionType.PROVINCE.getCode();
        }
    }

    /**
     * 获取对应redis中的hash的key
     * @param parentId
     * @return
     */
    private String getCacheRedisKey(Long parentId){
        //获取相应地区类型对应在agencyId的位数
        return RedisKey.SYS_REGION_SONS_CACHE_PREFIX +getPrefixOfAgencyId(parentId);
    }

    /**
     * 查询对应地区之下的地区
     * @param id
     * @return
     */
    public List<SysRegion> getSonRegion(Long id){
        //从缓存中查询
        String redisKey = getCacheRedisKey(id);
        String sysRegionListJson = String.valueOf(customRedisTemplate.opsForValue().get(redisKey));
        List<SysRegion> sysRegions = null;
        if(StringUtils.isNotBlank(sysRegionListJson)){
            sysRegions = JSONObject.parseArray(sysRegionListJson,SysRegion.class);
        }
        if(CollectionUtils.isNotEmpty(sysRegions)){
            return sysRegions;
        }
        //不存在则异步开启刷新缓存任务
        refreshCacheAsync();
        //从db中查询
        return getSonRegionNoCache(id);
    }

    public List<SysRegion> getSonRegionNoCache(Long id){
        //从db中查询
        SysRegion param = new SysRegion();
        param.setParentId(id);
        return mapper.select(param);
    }

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

    /**
     * 刷新其孩子节点到缓存，并对其孩子迭代执行此操作
     * @param id
     */
    private void refreshCacheRegionAndSon(Long id){
        List<SysRegion> sonRegions = getSonRegionNoCache(id);
        //把当前节点的子节点数据缓存
        String redisKey = getCacheRedisKey(id);
        customRedisTemplate.opsForValue().set(redisKey,JSON.toJSON(sonRegions).toString());
        log.info("完成地区【"+id+"】的子地区数据缓存");
        if(CollectionUtils.isEmpty(sonRegions) || getTypeFromId(id).equals(RegionType.CITY.getCode())){
            return;
        }
        for(SysRegion sonRegion:sonRegions){
            //缓存每个地区
            customRedisTemplate.opsForValue().set(RedisKey.SYS_REGION_CACHE_PREFIX+sonRegion.getId(),JSON.toJSON(sonRegion).toString());
            refreshCacheRegionAndSon(sonRegion.getId());
        }
    }

    /**
     * 5分钟内刷新数据到缓存
     */
    @Scheduled(cron = "0 */5 * * * *")//每5分钟刷新一次数据
    public void refreshCache(){

        String redisLockKey = RedisKey.SYS_REGION_REFRESH_LOCK +(DateTime.now().getMinuteOfDay()/5);//同一日每5分钟只刷新一次
        Boolean suc = customRedisTemplate.opsForValue().setIfAbsent(redisLockKey, String.valueOf(DateTime.now().getMillis()));
        if(!suc){
            log.info("刷新地区数据：获取乐观锁失败，不执行任务");
            return;
        }
        log.info("刷新地区数据任务开始");
        refreshCacheRegionAndSon(PARENT_ID_NONE);
        log.info("刷新常量数据任务成功");
    }


}
