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.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.ey.biz.framework.mybatis.core.util.MyBatisUtils;
import cn.ey.biz.module.system.api.category.dto.CategoryCodeExtraRespDTO;
import cn.ey.biz.module.system.controller.admin.category.vo.*;
import cn.ey.biz.module.system.convert.category.CategoryConvert;
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.mapper.category.CategoryAppBatChinsertMapper;
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.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.CategoryDataIdEnum;
import cn.ey.biz.module.system.enums.category.CategorySysFlagEnum;
import cn.ey.biz.module.system.service.config.ConfigService;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
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.Set;
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 CategoryV2ServiceImpl implements CategoryV2Service {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private CategoryMapper categoryMapper;

    @Resource
    private CategoryAppMapper categoryAppMapper;

    @Resource
    private CategoryDataMapper categoryDataMapper;

    @Resource
    private CategoryAppBatChinsertMapper categoryAppBatChinsertMapper;

    @Resource
    private ConfigService configService;

    @Resource
    private CategoryRedisDAO categoryRedisDAO;


    @Override
    @Transactional
    public Long createCategory(CategoryCreateReqVO createReqVO) {
        // 拼接ownerApp & code
        // ownerAppJoinCode(createReqVO);
        // 校验 code 是否存在（包含历史）
        checkCodeAll(createReqVO.getCode());
        // 校验 name 是否存在
        checkName(createReqVO.getName());
        CategoryDO category = CategoryConvert.INSTANCE.convert(createReqVO);
        initCategory(category);
        categoryMapper.insert(category);
        for (String app : createReqVO.getAppIds()) {
            CategoryAppDO categoryAppDO = new CategoryAppDO();
            categoryAppDO.setCategoryId(category.getCode());
            categoryAppDO.setApp(app);
            categoryAppMapper.insert(categoryAppDO);
        }
        resetCategoryCache();
        return category.getId();
    }

    @Override
    @Transactional
    public void updateCategory(CategoryUpdateReqVO updateReqVO) {
        // 校验存在
        this.validateCategoryExists(updateReqVO.getId());
        // 校验 name 是否存在
        checkNameUpdate(updateReqVO);

        CategoryDO categoryDO = categoryMapper.selectById(updateReqVO.getId());
        if (!categoryDO.getCode().equals(updateReqVO.getCode())) {
            // 校验 code 是否存在
            checkCode(updateReqVO.getCode());
            //更改 关联表CategoryData 中 code
            updateCategoryDataByCode(updateReqVO, categoryDO.getCode());
        }
        if (!updateReqVO.getAppIds().isEmpty()) {
            categoryAppInsertBatch(updateReqVO);
        }
        // 更新
        CategoryDO updateObj = CategoryConvert.INSTANCE.convert(updateReqVO);
        categoryMapper.updateById(updateObj);
        resetCategoryCache();
    }

    @Override
    public void deleteCategory(Long id) {
        // 校验存在
        this.validateCategoryExists(id);
        CategoryDO categoryDO = categoryMapper.selectById(id);

        // 系统类/业务通用字典不能删除
        if (CategorySysFlagEnum.SYSTEM.getCode().equals(categoryDO.getSysFlag())
                || CategorySysFlagEnum.BIZ_COMMON.getCode().equals(categoryDO.getSysFlag())
                || YesOrNoStatusEnum.NO.getCode() == categoryDO.getIsModify()) {
            throw ServiceExceptionUtil.exception(CATEGORY_SYSTEM_NOT_DEL);
        }
        //存在子级字典 无法删除
        Long aLong = categoryDataMapper.selectCount(CategoryDataDO::getCategoryCode, categoryDO.getCode());
        if (aLong > CategoryDataIdEnum.ROOT.getId()) {
            throw ServiceExceptionUtil.exception(CATEGORY_DATA_EXISTS_CHILDREN);
        }
        // 删除
        categoryMapper.deleteById(id);
        resetCategoryCache();
    }

    // 校验
    private void validateCategoryExists(Long id) {
        if (categoryMapper.selectById(id) == null) {
            throw exception(CATEGORY_NOT_EXISTS);
        }
    }

    @Override
    public CategoryDO getCategory(Long id) {
        CategoryDO categoryDO = categoryMapper.selectById(id);
        List<CategoryAppDO> categoryAppDOS = categoryAppMapper.selectListByCateGoryId(id);
        ArrayList<CategoryAppDO> list = new ArrayList<>(categoryAppDOS);
        categoryDO.setApps(list);
        return categoryDO;
    }

    @Override
    public List<CategoryDO> getCategoryList(Collection<Long> ids) {
        return categoryMapper.selectBatchIds(ids);
    }

    @Override
    public List<CategoryDO> getCategoryList(CategoryExportReqVO exportReqVO) {
        return categoryMapper.selectList(exportReqVO);
    }

    @Override
    public PageResult<CategoryRespVO> getCategoryPage(CategoryPageReqVO pageReqVO) {
        IPage page = MyBatisUtils.buildPage(pageReqVO);
        IPage<CategoryRespVO> result = categoryMapper.selectPage(page, pageReqVO);
        List<CategoryRespVO> categoryRespVOList = result.getRecords().stream().map(record -> {
            List<String> list = new ArrayList<>();
            for (CategoryAppRespVO app : record.getApps()) {
                list.add(app.getApp());
            }
            record.setAppIds(list);
            record.setApps(null);
            return record;
        }).collect(Collectors.toList());
        return new PageResult<>(categoryRespVOList, result.getTotal());
    }

    @Override
    @Cacheable(value = RedisKeyConstants.DICT_DATA_CACHE_NAME_CATEGORY, key = "#root.methodName+'-'+#categoryCode", sync = true)
    public CategoryDO getCategoryCacheable(String categoryCode) {
        return categoryMapper.selectOne(new LambdaQueryWrapperX<CategoryDO>()
                .eq(CategoryDO::getCode, categoryCode)
                .eq(CategoryDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
                .eq(CategoryDO::getIsNewVersion, YesOrNoStatusEnum.YES.getCode())
        );
    }

    @Override
    @Cacheable(value = RedisKeyConstants.DICT_DATA_CACHE_NAME_CATEGORY, key = "'CategoryCodeList'", sync = true)
    public List<CategoryDO> getAllCategoryCodeList() {
        return categoryMapper.selectList(new LambdaQueryWrapperX<CategoryDO>()
                .eq(CategoryDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
                .eq(CategoryDO::getIsNewVersion, YesOrNoStatusEnum.YES.getCode()));
    }

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

    @Override
    public List<CategoryCodeExtraRespDTO> listCategoryCodeExtraInfo() {
        return categoryMapper.listCategoryCodeExtraInfo();
    }

    private void checkCode(String code) {
        CategoryDO categoryDO = categoryMapper.selectOne(new LambdaQueryWrapper<CategoryDO>()
                .eq(CategoryDO::getCode, code));
        if (categoryDO != null) {
            throw exception(CATEGORY_CODE_ALREADY_EXISTS);
        }
    }

    private void checkCodeAll(String code) {
        Long num = categoryMapper.countCategoryCodeNumber(code);
        if (num != null && num > 0) {
            throw exception(CATEGORY_CODE_ALREADY_EXISTS);
        }
    }

    private void checkName(String name) {
        CategoryDO categoryDO = categoryMapper.selectByName(name);
        if (categoryDO != null) {
            throw exception(CATEGORY_NAME_ALREADY_EXISTS);
        }
    }

    private void checkNameUpdate(CategoryUpdateReqVO updateReqVO) {
        CategoryDO categoryDO = categoryMapper.selectByName(updateReqVO.getName());
        if (categoryDO != null) {
            if (!updateReqVO.getId().equals(categoryDO.getId())) {
                throw exception(CATEGORY_NAME_ALREADY_EXISTS);
            }
        }
    }

    private void updateCategoryDataByCode(CategoryUpdateReqVO categoryDO, String code) {
        LambdaUpdateWrapper<CategoryDataDO> updateWrapper = Wrappers.<CategoryDataDO>update()
                .lambda()
                .eq(CategoryDataDO::getCategoryCode, code);
        CategoryDataDO categoryDataDO = new CategoryDataDO().setCategoryCode(categoryDO.getCode());
        categoryDataMapper.update(categoryDataDO, updateWrapper);
    }

    private void categoryAppInsertBatch(CategoryUpdateReqVO updateReqVO) {
        LambdaUpdateWrapper<CategoryAppDO> query = new LambdaUpdateWrapper<>();
        query.eq(CategoryAppDO::getCategoryId, updateReqVO.getCode());
        categoryAppMapper.delete(query);

        List<CategoryAppDO> category = updateReqVO.getAppIds().stream().map(app -> new CategoryAppDO()
                .setApp(app)
                .setCategoryId(updateReqVO.getCode())).collect(Collectors.toList());
        categoryAppBatChinsertMapper.saveBatch(category);
    }

    private void initCategory(CategoryDO categoryDO) {
        if (categoryDO != null) {
            if (categoryDO.getIsModify() == null
                    && CategorySysFlagEnum.BIZ_CUSTOM.getCode().equals(categoryDO.getSysFlag())) {
                categoryDO.setIsModify(YesOrNoStatusEnum.YES.getCode());
            }
            categoryDO.setCategoryVersion(1L);
            categoryDO.setIsNewVersion(YesOrNoStatusEnum.YES.getCode());
            categoryDO.setIsVersionControl(categoryDO.getIsVersionControl() == null
                    ? YesOrNoStatusEnum.NO.getCode() : categoryDO.getIsVersionControl());
        }
    }

    private void ownerAppJoinCode(CategoryBaseVO categoryBaseVO) {
        String joinStr = StrUtil.join(StrUtil.UNDERLINE, categoryBaseVO.getOwnerApp(), categoryBaseVO.getCode());
        categoryBaseVO.setCode(joinStr);
    }

}
