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

import cn.ey.biz.common.exception.util.ServiceExceptionUtil;
import cn.ey.biz.common.pojo.PageResult;
import cn.ey.biz.framework.mybatis.core.util.MyBatisUtils;
import cn.ey.biz.module.system.api.category.dto.DictCatalogDTO;
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.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.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.YesOrNoStatusEnum;
import cn.ey.biz.module.system.enums.category.CategoryDataHasChildrenEnum;
import cn.ey.biz.module.system.enums.category.CategoryDataIdEnum;
import cn.ey.biz.module.system.service.config.ConfigService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
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 实现类
 */
@Service
@Validated
public class CategoryDataServiceImpl implements CategoryDataService {

    @Resource
    private CategoryDataMapper categoryDataMapper;

    @Resource
    private CategoryMapper categoryMapper;

    @Resource
    private CategoryAppMapper categoryAppMapper;

    @Resource
    private I18nDictMapper i18nDictMapper;

    @Resource
    private I18nDictBatchInsertMapper i18nDictBatchInsertMapper;

    @Resource
    private ConfigService configService;

    @Resource
    private CategoryRedisDAO categoryRedisDAO;

    @Override
    @Transactional
    public Long createCategoryData(CategoryDataCreateReqVO createReqVO) {
        // 校验父级字典是否存在
        checkParentResource(createReqVO.getPid(), null);
        // 校验名称
//        checkNameRepetition(createReqVO.getCategoryCode(),createReqVO.getName());
        checkNameRepetition(createReqVO);
        // 插入
        CategoryDataDO categoryData = CategoryDataConvert.INSTANCE.convert(createReqVO);
        initCategoryData(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())
                    .setLang(lang.getLang())
                    .setLabel(lang.getLabel());
            i18nDictDOs.add(i18nDictDO);
        }
        i18nDictBatchInsertMapper.saveBatch(i18nDictDOs);
        this.resetCategoryDataCache();
        // 返回
        return categoryData.getId();
    }

    @Override
    @Transactional
    public void updateCategoryData(CategoryDataUpdateReqVO updateReqVO) {
        // 校验父级字典是否存在
        checkParentResource(updateReqVO.getPid(), null);
        //校验名称
//        checkNameRepetition(updateReqVO);
        checkNameRepetition(updateReqVO);
        // 校验存在
        this.validateCategoryDataExists(updateReqVO.getId());
        // 更新
        CategoryDataDO updateObj = CategoryDataConvert.INSTANCE.convert(updateReqVO);
        categoryDataMapper.updateById(updateObj);

        // 更新到多语言
        LambdaQueryWrapper<I18nDictDO> queryWrapper = Wrappers.<I18nDictDO>lambdaQuery()
                .eq(I18nDictDO::getKey, 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())
                    .setLang(lang.getLang())
                    .setLabel(lang.getLabel());
            i18nDictDOs.add(i18nDictDO);
        }
        i18nDictBatchInsertMapper.saveBatch(i18nDictDOs);
        this.resetCategoryDataCache();
    }

    @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<CategoryDataRespVO>(result.getRecords(), result.getTotal());
    }

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

    @Override
    public List<CategoryDataDO> getCategoryByidData(Long pid) {

        List<CategoryDataDO> categoryByidData = categoryDataMapper.getCategoryByidData(pid);
        // 校验是否有子级字典
        if (categoryByidData == null || categoryByidData.size() == 0) {
            throw ServiceExceptionUtil.exception(CATEGORY_DATA_CHILDREN_NOT_EXISTS);
        }
        return categoryByidData;
    }

    @Override
    public List<CategoryDataSimpleRespVO> getCategoryDatas(String app, String acceptLanguage) {
        List<CategoryDataSimpleRespVO> list = categoryDataMapper.selectLists(app, acceptLanguage);
        return list;
    }

    @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
    public CategoryDataDO getCategoryDataKey(String app, String category, String key) {
        if (StringUtils.isEmpty(app) || StringUtils.isEmpty(category) || StringUtils.isEmpty(key)) {
            throw exception(CATEGORY_DATA_ARGUMENTS_NOT_NULL);
        }
        return categoryDataMapper.getCategoryDataKey(app, category, key);
    }

    @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;
    }

    private void initCategoryData(CategoryDataDO categoryDataDO) {
        if (categoryDataDO != null) {
            categoryDataDO.setCategoryVersion(1L);
            categoryDataDO.setDataVersion(1L);
            categoryDataDO.setNewVersion(YesOrNoStatusEnum.YES.getCode());
        }
    }


    private 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);
    }
}
