package cn.iocoder.yudao.module.system.service.dict;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.dataobject.dict.CategoryDO;
import cn.iocoder.yudao.module.system.dal.mysql.dept.DeptMapper;
import cn.iocoder.yudao.module.system.dal.mysql.dict.CategoryMapper;
import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.util.*;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.DEPT_NAME_DUPLICATE;

/**
 * 分类字典 Service 实现类
 *
 * @author vinjor-m
 */
@Service
@Validated
@Slf4j
public class CategoryServiceImpl implements CategoryService {
    @Resource
    private CategoryMapper categoryMapper;
    /**
     * 创建分类字典
     *
     * @param categoryDO 分类字典信息
     * @return 分类字典主键id
     */
    @Override
    @CacheEvict(cacheNames = RedisKeyConstants.CATEGORY_CHILDREN_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，因为操作一个分类字典，涉及到多个缓存
    public String createCategory(CategoryDO categoryDO) {
        if (categoryDO.getPid() == null) {
            categoryDO.setPid(CategoryDO.PARENT_ID_ROOT);
        }
        // 校验父级的有效性
        validateParentCategory(null, categoryDO.getPid());
        // 校验父级的唯一性
        validateCategoryCodeUnique(null, categoryDO.getPid(), categoryDO.getCode());

        // 插入字典
        categoryMapper.insert(categoryDO);
        return categoryDO.getId();
    }

    /**
     * 更新分类字典
     *
     * @param updateReqVO 分类字典信息
     */
    @Override
    @CacheEvict(cacheNames = RedisKeyConstants.CATEGORY_CHILDREN_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，因为操作一个部门，涉及到多个缓存
    public void updateCategory(CategoryDO updateReqVO) {
        if (updateReqVO.getPid() == null) {
            updateReqVO.setPid(CategoryDO.PARENT_ID_ROOT);
        }
        // 校验自己存在
        validateCategoryExists(updateReqVO.getId());
        // 校验父级的有效性
        validateParentCategory(updateReqVO.getId(), updateReqVO.getPid());
        // 校验code的唯一性
        validateCategoryCodeUnique(updateReqVO.getId(), updateReqVO.getPid(), updateReqVO.getCode());

        // 更新
        categoryMapper.updateById(updateReqVO);
    }

    /**
     * 删除分类字典
     *
     * @param id 分类字典id
     */
    @Override
    @CacheEvict(cacheNames = RedisKeyConstants.CATEGORY_CHILDREN_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，因为操作一个部门，涉及到多个缓存
    public void deleteCategory(String id) {
        // 校验是否存在
        validateCategoryExists(id);
        // 校验是否有子部门
        if (categoryMapper.selectCountByParentId(id) > 0) {
            throw exception(CATEGORY_EXITS_CHILDREN);
        }
        // 删除部门
        categoryMapper.deleteById(id);
    }

    /**
     * 批量删除分类字典
     *
     * @param ids 分类字典ids
     * @author vinjor-M
     * @date 17:16 2024/8/3
     **/
    @Override
    @CacheEvict(cacheNames = RedisKeyConstants.CATEGORY_CHILDREN_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，因为操作一个部门，涉及到多个缓存
    public void deleteCategory(Set<String> ids) {
        categoryMapper.deleteByIds(ids);
    }

    @VisibleForTesting
    void validateCategoryExists(String id) {
        if (id == null) {
            return;
        }
        CategoryDO categoryDO = categoryMapper.selectById(id);
        if (categoryDO == null) {
            throw exception(CATEGORY_NOT_FOUND);
        }
    }

    @VisibleForTesting
    void validateParentCategory(String id, String parentId) {
        if (parentId == null || CategoryDO.PARENT_ID_ROOT.equals(parentId)) {
            return;
        }
        // 1. 不能设置自己为父级
        if (Objects.equals(id, parentId)) {
            throw exception(CATEGORY_PARENT_ERROR);
        }
        // 2. 父级不存在
        CategoryDO parent = categoryMapper.selectById(parentId);
        if (parent == null) {
            throw exception(CATEGORY_PARENT_NOT_EXITS);
        }
        // 3. 递归校验父级，如果父级是自己的子级，则报错，避免形成环路
        if (id == null) { // id 为空，说明新增，不需要考虑环路
            return;
        }
        for (int i = 0; i < Short.MAX_VALUE; i++) {
            // 3.1 校验环路
            parentId = parent.getPid();
            if (Objects.equals(id, parentId)) {
                throw exception(CATEGORY_PARENT_IS_CHILD);
            }
            // 3.2 继续递归下一级父级
            if (parentId == null || CategoryDO.PARENT_ID_ROOT.equals(parentId)) {
                break;
            }
            parent = categoryMapper.selectById(parentId);
            if (parent == null) {
                break;
            }
        }
    }

    @VisibleForTesting
    void validateCategoryCodeUnique(String id, String parentId, String code) {
        CategoryDO categoryDO = categoryMapper.selectByParentIdAndCode(parentId, code);
        if (categoryDO == null) {
            return;
        }
        // 如果 id 为空，说明不用比较是否为相同 id 的code
        if (id == null) {
            throw exception(CATEGORY_CODE_DUPLICATE);
        }
        if (ObjectUtil.notEqual(categoryDO.getId(), id)) {
            throw exception(CATEGORY_CODE_DUPLICATE);
        }
    }

    /**
     * 获得分类字典信息
     *
     * @param id 分类字典编号
     * @return 分类字典信息
     */
    @Override
    public CategoryDO getCategory(String id) {
        return categoryMapper.selectById(id);
    }

    /**
     * 获得分类字典信息数组
     *
     * @param ids 分类字典id数组
     * @return 分类字典信息数组
     */
    @Override
    public List<CategoryDO> getCategoryList(Collection<String> ids) {
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        return categoryMapper.selectBatchIds(ids);
    }

    /**
     * 筛选分类字典列表
     *
     * @param reqVO 筛选条件请求 VO
     * @return 分类字典列表
     */
    @Override
    public List<CategoryDO> getCategoryList(CategoryDO reqVO) {
        return categoryMapper.selectList(reqVO);
    }

    /**
     * 获得指定分类字典的所有子分类字典
     *
     * @param id 分类字典id
     * @return 子分类字典列表
     */
    @Override
    public List<CategoryDO> getChildCategoryList(String id) {
        List<CategoryDO> children = new LinkedList<>();
        // 遍历每一层
        Collection<String> parentIds = Collections.singleton(id);
        for (int i = 0; i < Short.MAX_VALUE; i++) { // 使用 Short.MAX_VALUE 避免 bug 场景下，存在死循环
            // 查询当前层，所有的子部门
            List<CategoryDO> catgs = categoryMapper.selectListByParentId(parentIds);
            // 1. 如果没有子部门，则结束遍历
            if (CollUtil.isEmpty(catgs)) {
                break;
            }
            // 2. 如果有子部门，继续遍历
            children.addAll(catgs);
            parentIds = convertSet(catgs, CategoryDO::getId);
        }
        return children;
    }

    /**
     * 获得所有子分类字典，从缓存中
     *
     * @param id 父分类字典id
     * @return 子分类字典列表
     */
    @Override
    @DataPermission(enable = false) // 禁用数据权限，避免建立不正确的缓存
    @Cacheable(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, key = "#id")
    public Set<String> getChildCategoryIdListFromCache(String id) {
        List<CategoryDO> children = getChildCategoryList(id);
        return convertSet(children, CategoryDO::getId);
    }

    /**
     * 校验分类字典们是否有效。如下情况，视为无效：
     * 1. 分类字典id不存在
     *
     * @param ids 分类字典id数组
     */
    @Override
    public void validateCategoryList(Collection<String> ids) {
        if (CollUtil.isEmpty(ids)) {
            return;
        }
        // 获得字典信息
        Map<String, CategoryDO> catgMap = getCategoryMap(ids);
        // 校验
        ids.forEach(id -> {
            CategoryDO catg = catgMap.get(id);
            if (catg == null) {
                throw exception(CATEGORY_NOT_FOUND);
            }
        });
    }
}
