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

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjUtil;
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.framework.datapermission.core.util.DataPermissionUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.dataobject.deptsaporg.DeptSapOrgDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.dept.DeptMapper;
import cn.iocoder.yudao.module.system.dal.mysql.deptsaporg.DeptSapOrgMapper;
import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper;
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.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

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

/**
 * 部门 Service 实现类
 *
 * @author 芋道源码
 */
@Service
@Validated
@Slf4j
public class DeptServiceImpl implements DeptService {

    @Resource
    private DeptMapper deptMapper;
    @Resource
    private DeptSapOrgMapper deptSapOrgMapper;
    @Resource
    private AdminUserMapper userMapper;

    @Override
    @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，因为操作一个部门，涉及到多个缓存
    @Transactional(rollbackFor = Exception.class)
    public Long createDept(DeptSaveReqVO createReqVO) {
        if (createReqVO.getParentId() == null) {
            createReqVO.setParentId(DeptDO.PARENT_ID_ROOT);
        }
        // 校验父部门的有效性
        validateParentDept(null, createReqVO.getParentId());
        // 校验部门名的唯一性
        validateDeptNameUnique(null, createReqVO.getParentId(), createReqVO.getName());

        // 插入部门
        DeptDO dept = BeanUtils.toBean(createReqVO, DeptDO.class);
        deptMapper.insert(dept);
        // 2.2 插入关联SAP工厂
        if (CollectionUtil.isNotEmpty(dept.getFactoryIds())) {
            deptSapOrgMapper.insertBatch(convertList(dept.getFactoryIds(),
                    factoryId -> new DeptSapOrgDO().setDeptId(dept.getId()).setFactoryId(factoryId)));
        }
        // 2.2 插入关联SAP成本中心
        if (CollectionUtil.isNotEmpty(dept.getCostCenterIds())) {
            deptSapOrgMapper.insertBatch(convertList(dept.getCostCenterIds(),
                    costCenterId -> new DeptSapOrgDO().setDeptId(dept.getId()).setCostCenterId(costCenterId)));
        }
        // 2.2 插入关联SAP库存地点
        if (CollectionUtil.isNotEmpty(dept.getStockLocationIds())) {
            deptSapOrgMapper.insertBatch(convertList(dept.getStockLocationIds(),
                    stockLocationId -> new DeptSapOrgDO().setDeptId(dept.getId()).setStockLocationId(stockLocationId)));
        }

        return dept.getId();
    }

    @Override
    @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，因为操作一个部门，涉及到多个缓存
    @Transactional(rollbackFor = Exception.class)
    public void updateDept(DeptSaveReqVO updateReqVO) {
        if (updateReqVO.getParentId() == null) {
            updateReqVO.setParentId(DeptDO.PARENT_ID_ROOT);
        }
        // 校验自己存在
        validateDeptExists(updateReqVO.getId());
        // 校验父部门的有效性
        validateParentDept(updateReqVO.getId(), updateReqVO.getParentId());
        // 校验部门名的唯一性
        validateDeptNameUnique(updateReqVO.getId(), updateReqVO.getParentId(), updateReqVO.getName());
        // 更新部门
        DeptDO updateObj = BeanUtils.toBean(updateReqVO, DeptDO.class);

        deptMapper.updateById(updateObj);

        // 2.2 更新SAP工厂/成本中心/库存地点
        updateSapOrg(updateReqVO, updateObj);
    }

    private void updateSapOrg(DeptSaveReqVO updateReqVO, DeptDO updateObj) {
        Long deptId = updateReqVO.getId();
        // 查询部门已经关联的SAP工厂
        Set<Long> dbFactoryIds = convertSet(deptSapOrgMapper.selectListByDeptId(deptId), DeptSapOrgDO::getFactoryId);
        // 查询部门已经关联的SAP成本中心
        Set<Long> dbCostCenterIds = convertSet(deptSapOrgMapper.selectListByDeptId(deptId), DeptSapOrgDO::getCostCenterId);
        // 查询部门已经关联的SAP库存地点
        Set<Long> dbStockLocationIds = convertSet(deptSapOrgMapper.selectListByDeptId(deptId), DeptSapOrgDO::getStockLocationId);
        // 计算新增和删除的SAP工厂编号
        Set<Long> factoryIds = CollUtil.emptyIfNull(updateObj.getFactoryIds());
        Collection<Long> createFactoryIds = CollUtil.subtract(factoryIds, dbFactoryIds);
        Collection<Long> deleteFactoryIds = CollUtil.subtract(dbFactoryIds, factoryIds);
        // 执行新增和删除。对于已经授权的 SAP工厂，不用做任何处理
        if (!CollectionUtil.isEmpty(createFactoryIds)) {
            deptSapOrgMapper.insertBatch(convertList(createFactoryIds,
                    factoryId -> new DeptSapOrgDO().setDeptId(deptId).setFactoryId(factoryId)));
        }
        if (!CollectionUtil.isEmpty(deleteFactoryIds)) {
            deptSapOrgMapper.deleteByDeptIdAndFactoryId(deptId, deleteFactoryIds);
        }
        // 计算新增和删除的SAP成本中心
        Set<Long> costCenterIds = CollUtil.emptyIfNull(updateObj.getCostCenterIds());
        Collection<Long> createCostCenterIds = CollUtil.subtract(costCenterIds, dbCostCenterIds);
        Collection<Long> deleteCostCenterIds = CollUtil.subtract(dbCostCenterIds, costCenterIds);
        // 执行新增和删除。对于已经授权的 SAP成本中心，不用做任何处理
        if (!CollectionUtil.isEmpty(createCostCenterIds)) {
            deptSapOrgMapper.insertBatch(convertList(createCostCenterIds,
                    costCenterId -> new DeptSapOrgDO().setDeptId(deptId).setCostCenterId(costCenterId)));
        }
        if (!CollectionUtil.isEmpty(deleteCostCenterIds)) {
            deptSapOrgMapper.deleteByDeptIdAndCostCenterId(deptId, deleteCostCenterIds);
        }
        // 计算新增和删除的SAP库存地点
        Set<Long> stockLocationIds = CollUtil.emptyIfNull(updateObj.getStockLocationIds());
        Collection<Long> createStockLocationIds = CollUtil.subtract(stockLocationIds, dbStockLocationIds);
        Collection<Long> deleteStockLocationIds = CollUtil.subtract(dbStockLocationIds, stockLocationIds);
        // 执行新增和删除。对于已经授权的 SAP库存地点，不用做任何处理
        if (!CollectionUtil.isEmpty(createStockLocationIds)) {
            deptSapOrgMapper.insertBatch(convertList(createStockLocationIds,
                    stockLocationId -> new DeptSapOrgDO().setDeptId(deptId).setStockLocationId(stockLocationId)));
        }
        if (!CollectionUtil.isEmpty(deleteStockLocationIds)) {
            deptSapOrgMapper.deleteByDeptIdAndStockLocationId(deptId, deleteStockLocationIds);
        }
    }

    @Override
    @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，因为操作一个部门，涉及到多个缓存
    @Transactional(rollbackFor = Exception.class)
    public void deleteDept(Long id) {
        // 校验是否存在
        validateDeptExists(id);
        // 校验是否有子部门
        if (deptMapper.selectCountByParentId(id) > 0) {
            throw exception(DEPT_EXITS_CHILDREN);
        }
        // 删除部门
        deptMapper.deleteById(id);
    }

    @VisibleForTesting
    void validateDeptExists(Long id) {
        if (id == null) {
            return;
        }
        DeptDO dept = deptMapper.selectById(id);
        if (dept == null) {
            throw exception(DEPT_NOT_FOUND);
        }
    }

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

    @VisibleForTesting
    void validateDeptNameUnique(Long id, Long parentId, String name) {
        DeptDO dept = deptMapper.selectByParentIdAndName(parentId, name);
        if (dept == null) {
            return;
        }
        // 如果 id 为空，说明不用比较是否为相同 id 的部门
        if (id == null) {
            throw exception(DEPT_NAME_DUPLICATE);
        }
        if (ObjectUtil.notEqual(dept.getId(), id)) {
            throw exception(DEPT_NAME_DUPLICATE);
        }
    }

    @Override
    public DeptDO getDept(Long id) {
        return deptMapper.selectById(id);
    }

    @Override
    @DataPermission(enable = false)
    public DeptDO getDeptNoPermission(Long id) {
        return deptMapper.selectById(id);
    }

    @Override
    public List<DeptDO> getDeptList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        return deptMapper.selectBatchIds(ids);
    }

    @Override
    public List<DeptDO> getDeptList(DeptListReqVO reqVO) {
        List<DeptDO> list = deptMapper.selectList(reqVO);
        list.sort(Comparator.comparing(DeptDO::getSort));
        return list;
    }

    @Override
    public List<DeptDO> getDeptListByParentId(DeptListReqVO reqVO) {
        List<Long> deptIds = new ArrayList<>();
        deptIds.add(reqVO.getDeptId());
        return deptMapper.selectListByParentId(deptIds);
    }

    @Override
    public List<DeptDO> ignoredTenantDepts(DeptListReqVO reqVO) {
        return deptMapper.allDepts();
    }

    @Override
    public List<DeptDO> companyLevelDepts(DeptListReqVO reqVO) {
        Set<Long> parentIds = new HashSet<>();
        parentIds.add(DeptDO.PARENT_ID_ROOT);

        // 存储最终需要返回的部门列表
        List<DeptDO> resultDepts = new ArrayList<>();

        // 查询当前登录人所属部门
        Long userId = SecurityFrameworkUtils.getLoginUserId();
        AdminUserDO user = userMapper.selectById(userId);
        if (ObjUtil.isEmpty(user)) {
            return resultDepts;
        }
        AtomicReference<Long> deptId = new AtomicReference<>(user.getDeptId());
        if (CollUtil.isNotEmpty(reqVO.getDeptIds())) {
            // 如果传递了参数 则是要查询指定部门的所有子部门
            reqVO.getDeptIds().forEach(departmentId -> {
                deptId.set(departmentId);
            });
        }

        DataPermissionUtils.executeIgnore(() -> {
            DeptDO dept = getDept(deptId.get());
            if (ObjUtil.isEmpty(dept)) {
                throw exception(DEPT_NOT_FOUND);
            }

            // 一级部门
            List<DeptDO> theFirstLevelDepts = deptMapper.selectListByParentId(parentIds);
            System.out.println("顶级部门数量：" + theFirstLevelDepts.size());
            if (CollUtil.isNotEmpty(theFirstLevelDepts)) {
                theFirstLevelDepts.forEach(department -> {
                    if (dept.getId().equals(department.getId())) {
                        // 当前登录人所属部门是 一级部门
                        // 此时需要返回一级部门的二级公司子部门
                        parentIds.clear();
                        parentIds.add(department.getId());
                        List<DeptDO> theSecondLevelDepts = deptMapper.selectListByParentId(parentIds);
                        System.out.println("二级部门数量：" + theSecondLevelDepts.size());
                        resultDepts.addAll(theSecondLevelDepts);
                    }
                    // 当前登录人所属部门是二级部门或二级部门的子部门
                    if (!dept.getId().equals(department.getId())) {
                        // 查询当前登录人所属部门的父部门 追溯到二级部门
                        // 递归查询当前登录人所属部门的父部门 直到找到 二级部门(parentId=department.getId())
                        // 如果 parentDept.getParentId().equals(department.getId()) 说明parentDept部门已经是二级部门了
                        DeptDO currentDept = dept;
                        if (currentDept.getParentId().equals(department.getId())) {
                            // 当前部门是二级部门(父部门是一级部门)
                            if (!resultDepts.contains(dept)) {
                                resultDepts.add(dept);
                            }
                        }
                        while (currentDept != null && !currentDept.getParentId().equals(DeptDO.PARENT_ID_ROOT)) {
                            DeptDO tempParentDept = deptMapper.selectById(currentDept.getParentId());
                            if (tempParentDept == null) {
                                break;
                            }
                            if (currentDept.getParentId().equals(department.getId())) {
                                // 当前部门是二级部门(父部门是一级部门)
                                if (!resultDepts.contains(dept)) {
                                    resultDepts.add(currentDept);
                                }
                                break;
                            }
                            currentDept = tempParentDept;
                        }
                    }
                });
            }
        });
        return resultDepts;
    }

    @Override
    public List<DeptDO> getDeptByName(String deptName) {
        DeptListReqVO reqVO = new DeptListReqVO();
        reqVO.setName(deptName);
        return deptMapper.selectList(reqVO);
    }

    @Override
    @DataPermission(enable = false)
    public List<DeptDO> getDeptByNameNoPermission(String deptName) {
        DeptListReqVO reqVO = new DeptListReqVO();
        reqVO.setName(deptName);
        return deptMapper.selectList(reqVO);
    }

    @Override
    public List<DeptDO> getChildDeptList(Collection<Long> ids) {
        List<DeptDO> children = new LinkedList<>();
        // 遍历每一层
        Collection<Long> parentIds = ids;
        for (int i = 0; i < Short.MAX_VALUE; i++) { // 使用 Short.MAX_VALUE 避免 bug 场景下，存在死循环
            // 查询当前层，所有的子部门
            List<DeptDO> depts = deptMapper.selectListByParentId(parentIds);
            // 1. 如果没有子部门，则结束遍历
            if (CollUtil.isEmpty(depts)) {
                break;
            }
            // 2. 如果有子部门，继续遍历
            children.addAll(depts);
            parentIds = convertSet(depts, DeptDO::getId);
        }
        return children;
    }

    @Override
    public List<DeptDO> getDeptListByLeaderUserId(Long id) {
        return deptMapper.selectListByLeaderUserId(id);
    }

    @Override
    @DataPermission(enable = false) // 禁用数据权限，避免建立不正确的缓存
    @Cacheable(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, key = "#id")
    public Set<Long> getChildDeptIdListFromCache(Long id) {
        List<DeptDO> children = getChildDeptList(id);
        return convertSet(children, DeptDO::getId);
    }

    @Override
    public void validateDeptList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return;
        }
        // 获得科室信息
        Map<Long, DeptDO> deptMap = getDeptMap(ids);
        // 校验
        ids.forEach(id -> {
            DeptDO dept = deptMap.get(id);
            if (dept == null) {
                throw exception(DEPT_NOT_FOUND);
            }
            if (!CommonStatusEnum.ENABLE.getStatus().equals(dept.getStatus())) {
                throw exception(DEPT_NOT_ENABLE, dept.getName());
            }
        });
    }

    @Override
    public List<DeptDO> companyLevelChildrenDepts(DeptListReqVO deptListReqVO) {
        List<DeptDO> companyLevelDepts = companyLevelDepts(deptListReqVO);
        List<DeptDO> resultDepts = new ArrayList<>();
        // 查询每个公司级部门下的所有子部门
        if (CollUtil.isNotEmpty(companyLevelDepts)) {
            Set<Long> allDeptIds = new HashSet<>();
            resultDepts.addAll(companyLevelDepts);
            companyLevelDepts.forEach(dept -> {
                DataPermissionUtils.executeIgnore(() -> {
                    // 查询当前部门dept的所有子部门
                    Set<Long> tempDeptIds = getChildDeptIdListFromCache(dept.getId());
                    allDeptIds.addAll(tempDeptIds);
                });
            });
            // 查询 allDeptIds 对应的部门信息
            if (CollUtil.isNotEmpty(allDeptIds)) {
                DataPermissionUtils.executeIgnore(() -> {
                    DeptListReqVO reqVO = new DeptListReqVO();
                    reqVO.setDeptIds(allDeptIds);
                    List<DeptDO> childrenDepts = deptMapper.selectList(reqVO);
                    resultDepts.addAll(childrenDepts);
                });
            }
        }
        return resultDepts;
    }

    @Override
    public List<Long> selectedDepts(DeptListReqVO deptListReqVO) {
        Set<Long> allDeptIds = new HashSet<>();
        if (CollUtil.isNotEmpty(deptListReqVO.getDeptIds())) {
            deptListReqVO.getDeptIds().forEach(deptId -> {
                DataPermissionUtils.executeIgnore(() -> {
                    // 查询当前部门dept的所有子部门
                    Set<Long> tempDeptIds = getChildDeptIdListFromCache(deptId);
                    allDeptIds.add(deptId);
                    allDeptIds.addAll(tempDeptIds);
                });
            });
        }
        return new ArrayList<>(allDeptIds);
    }

}
