package cn.ey.biz.module.system.service.category.v2;

import cn.ey.biz.common.enums.CommonStatusEnum;
import cn.ey.biz.common.exception.util.ServiceExceptionUtil;
import cn.ey.biz.common.pojo.PageResult;
import cn.ey.biz.common.pojo.R;
import cn.ey.biz.framework.mybatis.core.util.MyBatisUtils;
import cn.ey.biz.module.system.api.category.dto.*;
import cn.ey.biz.module.system.controller.admin.app.vo.AppPageReqVO;
import cn.ey.biz.module.system.controller.admin.app.vo.AppRespVO;
import cn.ey.biz.module.system.controller.admin.category.vo.*;
import cn.ey.biz.module.system.controller.admin.i18n.vo.I18nRespVO;
import cn.ey.biz.module.system.convert.category.CategoryDataConvert;
import cn.ey.biz.module.system.convert.i18n.I18nDictConvert;
import cn.ey.biz.module.system.dal.dataobject.app.AppDO;
import cn.ey.biz.module.system.dal.dataobject.category.CategoryAppDO;
import cn.ey.biz.module.system.dal.dataobject.category.CategoryDO;
import cn.ey.biz.module.system.dal.dataobject.category.CategoryDataDO;
import cn.ey.biz.module.system.dal.dataobject.i18n.I18nDictDO;
import cn.ey.biz.module.system.dal.mapper.app.AppMapper;
import cn.ey.biz.module.system.dal.mapper.category.CategoryAppMapper;
import cn.ey.biz.module.system.dal.mapper.category.CategoryDataMapper;
import cn.ey.biz.module.system.dal.mapper.category.CategoryMapper;
import cn.ey.biz.module.system.dal.mapper.i18n.I18nDictBatchInsertMapper;
import cn.ey.biz.module.system.dal.mapper.i18n.I18nDictMapper;
import cn.ey.biz.module.system.dal.redis.RedisKeyConstants;
import cn.ey.biz.module.system.dal.redis.category.CategoryRedisDAO;
import cn.ey.biz.module.system.enums.StatusEnum;
import cn.ey.biz.module.system.enums.YesOrNoStatusEnum;
import cn.ey.biz.module.system.enums.category.*;
import cn.ey.biz.module.system.service.app.AppService;
import cn.ey.biz.module.system.service.category.dynamic.handler.CategoryRequestHandler;
import cn.ey.biz.module.system.service.config.ConfigService;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.redisson.api.RLock;
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.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.atomic.LongAdder;
import java.util.stream.Collectors;

import static cn.ey.biz.common.exception.util.ServiceExceptionUtil.exception;
import static cn.ey.biz.module.system.enums.ErrorCodeConstants.*;

/**
 * 字典分类数据 Service 实现类
 */
@Slf4j
@Service
@Validated
public class CategoryDataV2ServiceImpl implements CategoryDataV2Service {


    @Resource
    private CategoryDataMapper categoryDataMapper;

    @Resource
    private CategoryMapper categoryMapper;

    @Resource
    private CategoryAppMapper categoryAppMapper;

    @Resource
    private I18nDictMapper i18nDictMapper;

    @Resource
    private I18nDictBatchInsertMapper i18nDictBatchInsertMapper;

    @Resource
    private AppMapper appMapper;

    @Resource
    private CategoryRequestHandler categoryRequestHandler;

    @Resource
    private ConfigService configService;

    @Resource
    private CategoryRedisDAO categoryRedisDAO;

    @Resource
    private AppService appService;

    @Override
    @Transactional
    public Long createCategoryData(CategoryDataCreateReqVO createReqVO) {

        // 校验父级字典是否存在
        checkParentResource(createReqVO.getPid(), null);
        // 校验名称
        if (createReqVO.getCreateType() == null
                || CategoryCommonConstants.CreatedType.MANUAL.getCode().equals(createReqVO.getCreateType())) {
            checkNameRepetition(createReqVO);
        }
        // 插入
        CategoryDataDO categoryData = CategoryDataConvert.INSTANCE.convert(createReqVO);
        initCategoryData(categoryData);
        // 校验编码标识唯一
        checkDataIdentifier(categoryData);
        // 判断是否有子节点
        if (createReqVO.getPid() != null) {
            checkHaveOrNotChildNode(categoryData.getPid());
        }
        // 添加
        categoryDataMapper.insert(categoryData);
        // 添加到多语言
        List<I18nDictDO> i18nDictDOs = new ArrayList<>();
        List<String> list = Lists.newArrayList();
        for (I18nRespVO lang : createReqVO.getLangs()) {
            if (list.contains(lang.getLang() + lang.getKey())) {
                throw exception(I18N_LEAST_LANG_NOT_REPETITION, lang.getLang());
            } else {
                list.add(lang.getLang() + lang.getKey());
            }
            I18nDictDO i18nDictDO = new I18nDictDO()
                    .setKey(categoryData.getId())
                    .setDataIdentifier(categoryData.getDataIdentifier())
                    .setLang(lang.getLang())
                    .setLabel(lang.getLabel().trim());
            i18nDictDOs.add(i18nDictDO);
        }
        i18nDictBatchInsertMapper.saveBatch(i18nDictDOs);
        if (createReqVO.getCreateType() == null || CategoryCommonConstants.CreatedType.MANUAL.getCode().equals(createReqVO.getCreateType())) {
            this.resetCategoryDataCache();
        }
        return categoryData.getId();
    }

//    @Override
//    @Transactional
//    public void updateCategoryData(CategoryDataUpdateReqVO updateReqVO) {
//        // 校验父级字典是否存在
//        checkParentResource(updateReqVO.getPid(), null);
//        //校验名称
////        checkNameRepetition(updateReqVO);
//        if (updateReqVO.getCreateType() == null || CategoryCommonConstants.CreatedType.MANUAL.getCode().equals(updateReqVO.getCreateType())) {
//            checkNameRepetition(updateReqVO);
//        }
//        // 校验存在
//        this.validateCategoryDataExists(updateReqVO.getId());
//        // 更新
//        CategoryDataDO updateObj = CategoryDataConvert.INSTANCE.convert(updateReqVO);
//        categoryDataMapper.updateById(updateObj);
//        // 更新到多语言
//        LambdaQueryWrapper<I18nDictDO> queryWrapper = Wrappers.<I18nDictDO>lambdaQuery()
//                .eq(I18nDictDO::getKey, String.valueOf(updateReqVO.getId()));
//        i18nDictMapper.delete(queryWrapper);
//        List<I18nDictDO> i18nDictDOs = new ArrayList<>();
//        List<String> list = Lists.newArrayList();
//        for (I18nRespVO lang : updateReqVO.getLangs()) {
//            if (list.contains(lang.getLang() + lang.getKey())) {
//                throw exception(I18N_LEAST_LANG_NOT_REPETITION, lang.getLang());
//            } else {
//                list.add(lang.getLang() + lang.getKey());
//            }
//            I18nDictDO i18nDictDO = new I18nDictDO()
//                    .setKey(updateReqVO.getId())
//                    .setDataIdentifier(updateReqVO.getDataIdentifier())
//                    .setLang(lang.getLang())
//                    .setLabel(lang.getLabel());
//            i18nDictDOs.add(i18nDictDO);
//        }
//        i18nDictBatchInsertMapper.saveBatch(i18nDictDOs);
//        if (updateReqVO.getCreateType() == null || CategoryCommonConstants.CreatedType.MANUAL.getCode().equals(updateReqVO.getCreateType())) {
//            this.resetCategoryDataCache();
//        }
//    }

    @Override
    @Transactional
    public void updateCategoryData(CategoryDataUpdateReqVO updateReqVO) {
        RLock lock = categoryRedisDAO.getCategoryVersionLock(String.valueOf(updateReqVO.getId()));
        try {
            lock.lock();
            // 校验父级字典是否存在
            checkParentResource(updateReqVO.getPid(), null);
            //校验名称
//        checkNameRepetition(updateReqVO);
            if (updateReqVO.getCreateType() == null || CategoryCommonConstants.CreatedType.MANUAL.getCode().equals(updateReqVO.getCreateType())) {
                checkNameRepetition(updateReqVO);
            }
            // 校验存在
            this.validateCategoryDataExists(updateReqVO.getId());

            // 校验是否版本控制
            CategoryDO categoryDO = categoryMapper.selectOne(Wrappers.lambdaQuery(CategoryDO.class)
                    .eq(CategoryDO::getCode, updateReqVO.getCategoryCode())
                    .eq(CategoryDO::getIsNewVersion, YesOrNoStatusEnum.YES.code));
            if (ObjectUtil.equals(YesOrNoStatusEnum.NO.code, categoryDO.getStoreType())
                    && ObjectUtil.equals(YesOrNoStatusEnum.YES.code, categoryDO.getIsVersionControl())) {
                CategoryDataDO oldCategoryData = categoryDataMapper.selectById(updateReqVO.getId());

                // 将旧数据的new_version置为0
                CategoryDataDO updateCategoryData = CategoryDataDO.builder()
                        .id(oldCategoryData.getId())
                        .newVersion(YesOrNoStatusEnum.NO.code)
                        .build();
                categoryDataMapper.updateById(updateCategoryData);

                // 新增新版本数据
                CategoryDataDO categoryData = CategoryDataConvert.INSTANCE.convert(updateReqVO);
                categoryData.setId(null);
                categoryData.setNewVersion(YesOrNoStatusEnum.YES.code);
                categoryData.setHasChildren(oldCategoryData.getHasChildren());
                categoryData.setDataVersion(incrAndGet(categoryData.getDataVersion()));
                categoryData.setDataIdentifier(getDataIdentifier(categoryData));
                categoryDataMapper.insert(categoryData);

                // 更新子字典相关业务数据
                List<CategoryDataDO> subCategoryDataList = categoryDataMapper.selectList(Wrappers.<CategoryDataDO>lambdaQuery()
                        .eq(CategoryDataDO::getDeleted, Boolean.FALSE)
                        .eq(CategoryDataDO::getPid, oldCategoryData.getId()));
                subCategoryDataList.forEach(sub -> {
                    sub.setPid(categoryData.getId());
                    sub.setDataParentIdentifier(categoryData.getDataIdentifier());
                    categoryDataMapper.updateById(sub);
                });

                // 更新多语言数据:
                updateIl8nDict(updateReqVO.getId(), categoryData.getId(), categoryData.getDataIdentifier(), updateReqVO.getLangs());
            } else {
                // 更新
                CategoryDataDO updateObj = CategoryDataConvert.INSTANCE.convert(updateReqVO);
                categoryDataMapper.updateById(updateObj);

                //更新多语言数据
                updateIl8nDict(updateReqVO.getId(), updateReqVO.getId(), updateReqVO.getDataIdentifier(), updateReqVO.getLangs());
            }
            if (updateReqVO.getCreateType() == null || CategoryCommonConstants.CreatedType.MANUAL.getCode().equals(updateReqVO.getCreateType())) {
                this.resetCategoryDataCache();
            }
        }finally {
            lock.unlock();
        }
    }

    private void updateIl8nDict(Long oldId, Long newId, String dataIdentifier, List<I18nRespVO> i18nDictList) {
        // 更新到多语言
        LambdaQueryWrapper<I18nDictDO> queryWrapper = Wrappers.<I18nDictDO>lambdaQuery()
                .eq(I18nDictDO::getKey, String.valueOf(oldId));
        i18nDictMapper.delete(queryWrapper);
        List<I18nDictDO> i18nDictDOs = new ArrayList<>();
        List<String> list = Lists.newArrayList();
        for (I18nRespVO lang : i18nDictList) {
            if (list.contains(lang.getLang() + lang.getKey())) {
                throw exception(I18N_LEAST_LANG_NOT_REPETITION, lang.getLang());
            } else {
                list.add(lang.getLang() + lang.getKey());
            }
            I18nDictDO i18nDictDO = new I18nDictDO()
                    .setKey(newId)
                    .setDataIdentifier(dataIdentifier)
                    .setLang(lang.getLang())
                    .setLabel(lang.getLabel());
            i18nDictDOs.add(i18nDictDO);
        }
        i18nDictBatchInsertMapper.saveBatch(i18nDictDOs);
    }

    @Override
    @Transactional
    public void deleteCategoryData(Long id) {
        //校验是否还有子级字典
        if (categoryDataMapper.selectCountByParentId(id) > 0) {
            throw ServiceExceptionUtil.exception(CATEGORY_DATA_EXISTS_CHILDREN);
        }
        // 校验删除的字典是否存在
        if (categoryDataMapper.selectById(id) == null) {
            throw ServiceExceptionUtil.exception(CATEGORY_DATA_NOT_EXISTS);
        }
        // 校验存在
        this.validateCategoryDataExists(id);
        // 删除
        categoryDataMapper.deleteById(id);
        // 删除多语言内和字典关联的数据
        LambdaQueryWrapper<I18nDictDO> deleteWrapper = Wrappers.<I18nDictDO>lambdaQuery()
                .eq(I18nDictDO::getKey, id);
        i18nDictMapper.delete(deleteWrapper);
        this.resetCategoryDataCache();
    }

    private void validateCategoryDataExists(Long id) {
        if (categoryDataMapper.selectById(id) == null) {
            throw exception(CATEGORY_DATA_NOT_EXISTS);
        }
    }

    @Override
    public CategoryDataDO getCategoryData(Long id) {
        return categoryDataMapper.selectById(id);
    }

    @Override
    public List<CategoryDataDO> getCategoryDataList(Collection<Long> ids) {
        return categoryDataMapper.selectBatchIds(ids);
    }

    @Override
    public PageResult<CategoryDataRespVO> getCategoryDataPage(CategoryDataPageReqVO pageReqVO) {
        IPage page = MyBatisUtils.buildPage(pageReqVO);
        IPage<CategoryDataRespVO> result = categoryDataMapper.selectPage(page, pageReqVO);
        return new PageResult<>(result.getRecords(), result.getTotal());
    }

    @Override
    public List<CategoryDataDO> getCategoryDataList(CategoryDataExportReqVO exportReqVO) {
        return categoryDataMapper.selectList(exportReqVO);
    }

    @Override
    public List<CategoryDataRespVO> getCategoryByidData(Long pid, String acceptLanguage) {
        CategoryDataV2ReqDTO req = new CategoryDataV2ReqDTO();
        req.setDataPid(pid);
        req.setAcceptLanguage(acceptLanguage);
        req.setStatus(null);
        List<CategoryDataV2RespDTO> categoryData = categoryDataMapper.getCategoryDataV2(req);
        // 校验是否有子级字典
        if (categoryData == null || categoryData.isEmpty()) {
            throw ServiceExceptionUtil.exception(CATEGORY_DATA_CHILDREN_NOT_EXISTS);
        }
        List<CategoryDataRespVO> list = categoryData.stream()
                .map(CategoryDataConvert.INSTANCE::convertV2RespDto2RespVo)
                .collect(Collectors.toList());
        return list;
    }

    @Override
    public List<CategoryDataSimpleRespVO> getCategoryDatas(String app, String acceptLanguage, Integer status) {
        return categoryDataMapper.selectListsV2(app, acceptLanguage, status);
    }

    @Cacheable(value = RedisKeyConstants.DICT_DATA_CACHE_NAME_CATEGORY_DATA, key = "#root.methodName+'-'+#app+'-'+#acceptLanguage+'-'+#statusName", sync = true)
    @Override
    public Map<String, List<CategoryDataSimpleRespVO>> getCategoryDataTree(String app, String acceptLanguage, String statusName) {
        Integer status = getStatusCode(statusName);
        List<CategoryDataSimpleRespVO> list = categoryDataMapper.selectListsV2(app, acceptLanguage, status);
        ConcurrentMap<String, List<CategoryDataSimpleRespVO>> result = makeTree(list, CategoryDataIdEnum.ROOT.getId()).parallelStream().collect(Collectors.groupingByConcurrent(CategoryDataSimpleRespVO::getDictType));
        result.put(CategoryDataAppEnum.CODE.getApp(), getAppList(acceptLanguage));
        return result;
    }

    private List<CategoryDataSimpleRespVO> makeTree(List<CategoryDataSimpleRespVO> categoryDataList, Long pid) {
        List<CategoryDataSimpleRespVO> children = categoryDataList.parallelStream().filter(x -> x.getPid().equals(pid)).collect(Collectors.toList());
        List<CategoryDataSimpleRespVO> successor = categoryDataList.parallelStream().filter(x -> !x.getPid().equals(pid)).collect(Collectors.toList());
        List<CategoryDataSimpleRespVO> result = Collections.synchronizedList(new ArrayList<>());
        children.parallelStream().forEach(child -> {
            if (child.getHasChildren() != null && YesOrNoStatusEnum.YES.getCode() == child.getHasChildren()) {
                if (child.getChildren() == null) {
                    child.setChildren(new ArrayList<>());
                }
                makeTree(successor, child.getId()).forEach(y -> child.getChildren().add(y));
            }
            result.add(child);
        });
        return result;
    }

    @Override
    public List<CategoryDataSimpleRespVO> getCategoryDatas(String app, String acceptLanguage) {
        return this.getCategoryDatas(app, acceptLanguage, StatusEnum.ACTIVE.getCode());
    }

    @Override
    public CategoryDataRespVO getCategoryDataByid(Long id) {
        // 校验是否存在
        this.validateCategoryDataExists(id);
        CategoryDataDO categoryDataDO = categoryDataMapper.selectById(id);
        CategoryDataRespVO categoryDataRespVO = CategoryDataConvert.INSTANCE.convert(categoryDataDO);
        LambdaQueryWrapper<I18nDictDO> queryWrapper = Wrappers.<I18nDictDO>lambdaQuery()
                .eq(I18nDictDO::getKey, id);
        List<I18nDictDO> i18nDOS = i18nDictMapper.selectList(queryWrapper);
        List<I18nRespVO> i18nRespVOS = I18nDictConvert.INSTANCE.convertList(i18nDOS);
        categoryDataRespVO.setLangs(i18nRespVOS);
        return categoryDataRespVO;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void importDictCatalog(List<DictCatalogDTO> dictCatalogs) {
        List<DictCatalogDTO> categories = dictCatalogs.stream()
                .filter(dictCatalog -> dictCatalog.getPcode() == null
                        || dictCatalog.getPcode().isEmpty()).collect(Collectors.toList());

        // Category保存
        List<CategoryDO> categoryDOs = new ArrayList<>();
        List<CategoryDO> categoryDOsForNext = new ArrayList<>();
        categories.forEach(dictCatalog -> {
            CategoryDO categoryDO = categoryMapper.selectOne(new LambdaQueryWrapper<CategoryDO>()
                    .eq(CategoryDO::getCode, dictCatalog.getCode()));
            if (categoryDO == null) {
                categoryDO = new CategoryDO();
                categoryDO.setCode(dictCatalog.getCode());
                categoryDO.setName(dictCatalog.getName());
                categoryDO.setRemark(dictCatalog.getRemark());
                categoryDO.setStatus(dictCatalog.getStatus());
                categoryDO.setSysFlag(dictCatalog.getSysFlag());
                categoryDO.setType(dictCatalog.getType());
                categoryDO.setApp(dictCatalog.getAppIds());
                categoryDOs.add(categoryDO);
            }
            categoryDO.setApp(dictCatalog.getAppIds());
            categoryDOsForNext.add(categoryDO);
        });
        categoryMapper.insertBatch(categoryDOs);

        // Category-App 保存
        List<CategoryAppDO> appDOS = new ArrayList<>();
        categoryDOsForNext.forEach(categoryDO -> categoryDO.getApp().forEach(appId -> {
            CategoryAppDO appDO = categoryAppMapper.selectOne(new LambdaQueryWrapper<CategoryAppDO>()
                    .eq(CategoryAppDO::getCategoryId, categoryDO.getId())
                    .eq(CategoryAppDO::getApp, appId));
            if (appDO == null) {
                appDO = new CategoryAppDO();
                appDO.setApp(appId);
                appDO.setCategoryId(categoryDO.getCode());
                appDO.setRemark(categoryDO.getRemark());
                appDO.setStatus(categoryDO.getStatus());
                appDOS.add(appDO);
            }
        }));
        categoryAppMapper.insertBatch(appDOS);

        // 添加Data
        List<CategoryDataDO> data = new ArrayList<>();
        List<CategoryDataDO> dataForNext = new ArrayList<>();
        categoryDOsForNext.forEach(categoryDO -> {
            List<DictCatalogDTO> dataDto = dictCatalogs.stream().filter(dictCatalog ->
                    categoryDO.getCode().equals(dictCatalog.getPcode()))
                    .collect(Collectors.toList());
            dataDto.forEach(dictCatalogDTO -> {
                CategoryDataDO item = categoryDataMapper.selectOne(new LambdaQueryWrapper<CategoryDataDO>()
                        .eq(CategoryDataDO::getCategoryCode, categoryDO.getCode())
                        .eq(CategoryDataDO::getCode, dictCatalogDTO.getCode()));
                if (item == null) {
                    item = new CategoryDataDO();
                    item.setName(dictCatalogDTO.getName());
                    item.setValue(dictCatalogDTO.getValue());
                    item.setRemark(dictCatalogDTO.getRemark());
                    item.setStatus(dictCatalogDTO.getStatus());
                    item.setPid(0L);
                    item.setCode(dictCatalogDTO.getCode());
                    item.setCategoryCode(categoryDO.getCode());
                    item.setLangs(dictCatalogDTO.getLangs());
                    data.add(item);
                }
                item.setLangs(dictCatalogDTO.getLangs());
                dataForNext.add(item);
            });
        });
        categoryDataMapper.insertBatch(data);

        // 添加多语言
        List<I18nDictDO> i18nDictDOs = new ArrayList<>();
        dataForNext.forEach(item -> item.getLangs().forEach(lang -> {
            I18nDictDO i18nDictDO = i18nDictMapper.selectOne(new LambdaQueryWrapper<I18nDictDO>()
                    .eq(I18nDictDO::getKey, item.getId())
                    .eq(I18nDictDO::getLang, lang.getLang()));
            if (i18nDictDO == null) {
                i18nDictDO = new I18nDictDO();
                i18nDictDO.setKey(item.getId());
                i18nDictDO.setLang(lang.getLang());
                i18nDictDO.setLabel(lang.getLabel());
                i18nDictDOs.add(i18nDictDO);
            }
        }));
        i18nDictMapper.insertBatch(i18nDictDOs);
    }

    // 校验父级字典是否存在
    public void checkParentResource(Long pid, Long childId) {
        if (pid == null || CategoryDataIdEnum.ROOT.getId().equals(pid)) {
            return;
        }
        // 不能设置自己为父级字典
        if (pid.equals(childId)) {
            throw ServiceExceptionUtil.exception(CATEGORY_DATA_PARENT_ERROR);
        }
        CategoryDataDO category = categoryDataMapper.selectById(pid);
        // 父级字典不存在
        if (category == null) {
            throw ServiceExceptionUtil.exception(CATEGORY_DATA_NOT_EXISTENCE);
        }
    }

    // 判断是否有子节点
    private void checkHaveOrNotChildNode(Long pid) {
        CategoryDataDO categoryDataDO = categoryDataMapper.selectParentDictionaryByParentId(pid);
        // 如果是空的 就证明没有
        if (categoryDataDO == null) {
            return;
        }
        // 如果HasChildren为 1 证明已经有子节点 不需要进行判断
        if (categoryDataDO.getHasChildren().equals(CategoryDataHasChildrenEnum.HAVE.getHasChildren())) {
            return;
        }
        categoryDataDO.setHasChildren(CategoryDataHasChildrenEnum.HAVE.getHasChildren());
        categoryDataMapper.updateById(categoryDataDO);
    }

    // 添加校验
    private void checkNameRepetition(CategoryDataBaseVO reqVo) {
        if (reqVo.getLangs().size() <= 0) {
            throw exception(I18N_LEAST_ONE_DATA);
        }
        Long id = null;
        if (reqVo instanceof CategoryDataUpdateReqVO) {
            id = ((CategoryDataUpdateReqVO) reqVo).getId();
        }
        List<String> list = Lists.newArrayList();
        for (I18nRespVO lang : reqVo.getLangs()) {
            if (list.contains(lang.getLang() + lang.getLabel())) {
                throw exception(I18N_LEAST_DATA_DUPLICATE, lang.getLabel());
            } else {
                list.add(lang.getLang() + lang.getLabel());
            }
            CategoryDataRespVO categoryDataRespVO = categoryDataMapper.checkNameRepetition(reqVo, lang, id);
            if (categoryDataRespVO != null) {
                throw exception(I18N_LEAST_DATA_DUPLICATE, lang.getLabel());
            }
        }
    }

    private Integer hasChild(DictCatalogDTO target, List<DictCatalogDTO> dictCatalogs) {
        return dictCatalogs.stream().anyMatch(
                dictCatalog -> dictCatalog.getPcode().equals(target.getCode())) ? 1 : 0;
    }


    @Cacheable(value = RedisKeyConstants.DICT_DATA_CACHE_NAME_CATEGORY_DATA, key = "#root.methodName+'-'+#req", sync = true)
    @Override
    public List<CategoryDataV2RespDTO> getCategoryData(CategoryDataV2ReqDTO req) {
        log.debug("Query database dictionary data.");
        return categoryDataMapper.getCategoryDataV2(req);
    }

    @Override
    public List<CategoryDataV2RespDTO> getCategoryDataNonCache(CategoryDataV2ReqDTO req) {
        return categoryDataMapper.getCategoryDataV2(req);
    }

    @Cacheable(value = RedisKeyConstants.DICT_DATA_CACHE_NAME_CATEGORY_DATA, key = "#root.methodName+'-'+#app+'-'+#acceptLanguage+'-'+#statusName", sync = true)
    @Override
    public List<CategoryDataSimpleRespVO> getCategoryDataContainsApp(String app, String acceptLanguage, String statusName) {
        Integer status = getStatusCode(statusName);
        List<CategoryDataSimpleRespVO> list = this.getCategoryDatas(app, acceptLanguage, status);
        List<CategoryDataSimpleRespVO> appList = getAppList(acceptLanguage);
        list.addAll(appList);
        return list;
    }

    @NotNull
    private List<CategoryDataSimpleRespVO> getAppList(String acceptLanguage) {
        AppPageReqVO appPageReqVO = new AppPageReqVO();
        appPageReqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
        appPageReqVO.setPageSize(-1);
        List<AppRespVO> appI18nInfo = appService.getAppPage(appPageReqVO, acceptLanguage).getList();
        Map<String, String> appMap = appI18nInfo.stream().collect(Collectors.toMap(AppRespVO::getCode, e -> e.getName() == null ? "" : e.getName()));
        return appMapper.selectList(new LambdaQueryWrapper<AppDO>().eq(AppDO::getStatus, CommonStatusEnum.ENABLE.getStatus())).stream().map(appDO -> {
            CategoryDataSimpleRespVO respVO = new CategoryDataSimpleRespVO();
            String in18nName = appMap.get(appDO.getCode());
            respVO.setLabel(StringUtils.isEmpty(in18nName) ? appDO.getName() : in18nName);
            respVO.setDictType("app");
            respVO.setPid(0L);
            respVO.setDataValue(appDO.getAppRoad());
            respVO.setValue(appDO.getCode());
            return respVO;
        }).collect(Collectors.toList());
    }


    @Override
    public void resetCategoryDataCache() {
        // update refresh version
        Long categoryVersion = categoryRedisDAO.getCategoryVersion(RedisKeyConstants.DICT_REFRESH_VERSION_KEY);
        configService.updateConfigValueByCategoryAndKey("dict", "dict.refresh.version", String.valueOf(categoryVersion));
        // delete cache
        categoryRedisDAO.deleteGroup(RedisKeyConstants.DICT_DATA_CACHE_NAME_CATEGORY_DATA);
        log.info("缓存{}:*清除成功", RedisKeyConstants.DICT_DATA_CACHE_NAME_CATEGORY_DATA);
    }

    @Override
    public Map<String, List<Map<String, Object>>> listCustomCategoryDataByCategoryCodes(List<CategoryDataDynamicReqVO> categoryCodeReqList) {
        final int size = 1;
        if (CollUtil.isEmpty(categoryCodeReqList)) {
            throw exception(CATEGORY_DATA_REQUEST_PARAMETER_NON);
        }
        Map<String, List<Map<String, Object>>> result = new ConcurrentHashMap<>(16);
        List<List<CategoryDataDynamicReqVO>> partition = Lists.partition(new ArrayList<>(categoryCodeReqList), size);
        CompletableFuture<?>[] completableFutures = partition.stream().map(categoryCodePartition -> CompletableFuture.runAsync(() -> getCustomCategoryDataResult(result, categoryCodePartition))).toArray(CompletableFuture<?>[]::new);
        CompletableFuture<Void> completableFutureAll = CompletableFuture.allOf(completableFutures);
        completableFutureAll.join();
        return result;
    }

    @Override
    public PageResult<Map<String, Object>> pageCategoryDataByCategoryCodes(CategoryDataDynamicPageReqVO dataDynamicReqPageVO) {
        Map<String, Object> requestBody = new HashMap<>(16);
        if (dataDynamicReqPageVO.getQueryParams() != null && !dataDynamicReqPageVO.getQueryParams().isEmpty()) {
            requestBody.putAll(dataDynamicReqPageVO.getQueryParams());
        }
        Long tableNumber = categoryMapper.countTableNumber(dataDynamicReqPageVO.getCategoryCode());
        if (tableNumber == null || tableNumber == 0) {
            throw exception(CATEGORY_DATA_TABLE_NOT_EXISTS);
        }
        Map<String, Object> parameters = new HashMap<>(16);
        parameters.put(CategoryRequestHandler.PAGE_NO, dataDynamicReqPageVO.getPageNo());
        parameters.put(CategoryRequestHandler.PAGE_SIZE, dataDynamicReqPageVO.getPageSize());
        parameters.put(CategoryRequestHandler.CATEGORY_CODE, dataDynamicReqPageVO.getCategoryCode());
        parameters.put(CategoryRequestHandler.FUNCTION_TYPE, CategoryFunctionEnum.DEFAULT_PAGEFUL.getCode());
        R<?> invoke = categoryRequestHandler.invoke(parameters, requestBody);
        @SuppressWarnings("unchecked")
        PageResult<Map<String, Object>> dataResult = (PageResult<Map<String, Object>>) invoke.getData();
        return dataResult;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createBatchCategoryData(List<CategoryDataCreateReqVO> createReqVOList) {
        if (CollectionUtil.isNotEmpty(createReqVOList)) {
            createReqVOList.forEach(this::createCategoryData);
            this.resetCategoryDataCache();
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateBatchCategoryData(List<CategoryDataUpdateReqVO> updateReqVOList) {
        if (CollectionUtil.isNotEmpty(updateReqVOList)) {
            updateReqVOList.forEach(this::updateCategoryData);
            this.resetCategoryDataCache();
        }
    }

    @Override
    public Map<String, List<Map<String, Object>>> listDefaultCategoryDataByCategoryCodes(List<String> categoryCodeList) {
        return listDefaultCategoryDataByCodesAndStatusName(categoryCodeList, StatusEnum.ALL.getName());
    }

    @Override
    public Map<String, List<Map<String, Object>>> listDefaultCategoryDataByCodesAndStatusName(List<String> categoryCodeList, String statusName) {
        final int size = 1;
        if (CollUtil.isEmpty(categoryCodeList)) {
            throw exception(CATEGORY_DATA_REQUEST_PARAMETER_NON);
        }
        Set<String> categoryCodeSet = new HashSet<>(categoryCodeList);
        Map<String, List<Map<String, Object>>> result = new ConcurrentHashMap<>(16);
        List<List<String>> partition = Lists.partition(new ArrayList<>(categoryCodeSet), size);
        CompletableFuture<?>[] completableFutures = partition.stream().map(categoryCodePartition -> CompletableFuture.runAsync(() -> getDefaultCategoryDataResult(result, categoryCodePartition, statusName))).toArray(CompletableFuture<?>[]::new);
        CompletableFuture<Void> completableFutureAll = CompletableFuture.allOf(completableFutures);
        try {
            completableFutureAll.get();
        } catch (ExecutionException | InterruptedException e) {
            log.error("CategoryDataV2Service->listDefaultCategoryDataByCategoryCodes error:{}", e.getMessage(), e);
            throw exception(CATEGORY_DATA_REQUEST_ERROR,e.getCause().getMessage());
        }
        return result;
    }

    private void getCustomCategoryDataResult(Map<String, List<Map<String, Object>>> result, List<CategoryDataDynamicReqVO> categoryCodeReqList) {
        for (CategoryDataDynamicReqVO dataDynamicReqVO : categoryCodeReqList) {
            String categoryCode = dataDynamicReqVO.getCategoryCode();
            Map<String, Object> parameters = new ConcurrentHashMap<>(16);
            Map<String, Object> requestBody = null;
            if (dataDynamicReqVO.getQueryParams() != null && !dataDynamicReqVO.getQueryParams().isEmpty()) {
                requestBody = new ConcurrentHashMap<>(dataDynamicReqVO.getQueryParams());
            }
            parameters.put(CategoryRequestHandler.CATEGORY_CODE, categoryCode);
            parameters.put(CategoryRequestHandler.FUNCTION_TYPE, CategoryFunctionEnum.DEFAULT.getCode());
            R<?> invoke = categoryRequestHandler.invoke(parameters, requestBody);
            @SuppressWarnings("unchecked")
            List<Map<String, Object>> dataResult = (List<Map<String, Object>>) invoke.getData();
            result.put(categoryCode, dataResult);
        }
    }

    private void getDefaultCategoryDataResult(Map<String, List<Map<String, Object>>> result, List<String> categoryCodeList, String statusName) {
        for (String categoryCode : categoryCodeList) {
            Map<String, Object> parameters = new ConcurrentHashMap<>(16);
            parameters.put(CategoryRequestHandler.CATEGORY_CODE, categoryCode);
            parameters.put(CategoryRequestHandler.FUNCTION_TYPE, CategoryFunctionEnum.DEFAULT.getCode());
            Map<String, Object> requestBody = null;
            if (statusName != null) {
                requestBody = new HashMap<>(16);
                requestBody.put(CategoryRequestHandler.STATUS, statusName);
            }
            R<?> invoke = categoryRequestHandler.invoke(parameters, requestBody);
            @SuppressWarnings("unchecked")
            List<Map<String, Object>> dataResult = (List<Map<String, Object>>) invoke.getData();
            result.put(categoryCode, dataResult);
        }
    }

    private void initCategoryData(CategoryDataDO categoryDataDO) {
        if (categoryDataDO != null) {
            categoryDataDO.setCategoryVersion(categoryDataDO.getCategoryVersion() == null ? 1L : categoryDataDO.getCategoryVersion());
            categoryDataDO.setDataVersion(1L);
            categoryDataDO.setNewVersion(YesOrNoStatusEnum.YES.getCode());
            // category_code +  data_code + category_version + data_version
            String dataIdentifier = categoryDataDO.getDataIdentifier() == null
                    ? String.join(StrUtil.UNDERLINE, categoryDataDO.getCategoryCode(), categoryDataDO.getCode(),
                    String.valueOf(categoryDataDO.getCategoryVersion()), String.valueOf(categoryDataDO.getDataVersion()))
                    : categoryDataDO.getDataIdentifier();
            categoryDataDO.setDataIdentifier(dataIdentifier);
        }
    }

    private Integer getStatusCode(String statusName) {
        Integer status = StringUtils.isEmpty(statusName) ? StatusEnum.ACTIVE.getCode() : StatusEnum.getByName(statusName).getCode();
        if (StatusEnum.ALL.getName().equals(statusName)) {
            // ignore status
            status = null;
        }
        return status;
    }

    private void checkDataIdentifier(CategoryDataDO categoryData) {
        long count = categoryDataMapper.selectCount(Wrappers.lambdaQuery(CategoryDataDO.class)
                .eq(CategoryDataDO::getDeleted, Boolean.FALSE)
                .eq(CategoryDataDO::getCategoryCode, categoryData.getCategoryCode())
                .eq(CategoryDataDO::getCode, categoryData.getCode())
                .eq(CategoryDataDO::getNewVersion, YesOrNoStatusEnum.YES.getCode()));
        if (count > 0){
            throw ServiceExceptionUtil.exception(CATEGORY_DATA_IDENTIFIER_REPETITION);
        }
    }

    private Long incrAndGet(Long version) {
        return Objects.isNull(version) ? 1L : version + 1;
    }

    private String getDataIdentifier(CategoryDataDO categoryData) {
        return StrUtil.join(StrUtil.UNDERLINE,
                categoryData.getCategoryCode(),
                categoryData.getCode(),
                categoryData.getCategoryVersion(),
                categoryData.getDataVersion());
    }
}
