package com.njcn.rdms.module.system.service.dept;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.njcn.rdms.framework.common.enums.CommonStatusEnum;
import com.njcn.rdms.framework.common.util.object.BeanUtils;
import com.njcn.rdms.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
import com.njcn.rdms.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;
import com.njcn.rdms.module.system.dal.dataobject.dept.DeptDO;
import com.njcn.rdms.module.system.dal.mysql.dept.DeptMapper;
import com.njcn.rdms.module.system.dal.redis.RedisKeyConstants;
import com.njcn.rdms.module.system.enums.dept.DeptOrgTypeEnum;
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 jakarta.annotation.Resource;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.njcn.rdms.framework.common.util.collection.CollectionUtils.convertSet;
import static com.njcn.rdms.module.system.enums.ErrorCodeConstants.*;

/**
 * 部门 Service 实现类
 *
 * @author hongawen
 */
@Service
@Validated
@Slf4j
public class DeptServiceImpl implements DeptService {

    @Resource
    private DeptMapper deptMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，因为操作一个部门，涉及到多个缓存
    public Long createDept(DeptSaveReqVO createReqVO) {
        normalizeDeptCreateReq(createReqVO);
        // 校验父部门的有效性
        validateParentDept(null, createReqVO.getParentId());
        // 校验部门名的唯一性
        validateDeptNameUnique(null, createReqVO.getParentId(), createReqVO.getName());
        // 校验组织编码唯一性
        validateDeptCodeUnique(null, createReqVO.getCode());
        // 校验组织节点类型
        validateDeptOrgType(createReqVO.getOrgType());

        // 插入部门
        DeptDO dept = BeanUtils.toBean(createReqVO, DeptDO.class);
        deptMapper.insert(dept);
        fillDeptTreeFields(dept, createReqVO.getParentId());
        deptMapper.updateById(dept);
        return dept.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，因为操作一个部门，涉及到多个缓存
    public void updateDept(DeptSaveReqVO updateReqVO) {
        // 校验自己存在
        DeptDO oldDept = validateDeptExists(updateReqVO.getId());
        normalizeDeptUpdateReq(updateReqVO, oldDept);
        // 校验父部门的有效性
        validateParentDept(updateReqVO.getId(), updateReqVO.getParentId());
        // 校验部门名的唯一性
        validateDeptNameUnique(updateReqVO.getId(), updateReqVO.getParentId(), updateReqVO.getName());
        // 校验组织编码唯一性
        validateDeptCodeUnique(updateReqVO.getId(), updateReqVO.getCode());
        // 校验组织节点类型
        validateDeptOrgType(updateReqVO.getOrgType());

        // 更新部门
        DeptDO updateObj = BeanUtils.toBean(updateReqVO, DeptDO.class);
        fillDeptTreeFields(updateObj, updateReqVO.getParentId());
        deptMapper.updateById(updateObj);
        updateChildDeptTree(updateObj.getId(), oldDept.getPath(), oldDept.getLevel(), updateObj.getPath(), updateObj.getLevel());
    }

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

    @Override
    @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，因为操作一个部门，涉及到多个缓存
    public void deleteDeptList(List<Long> ids) {
        // 校验是否有子部门
        for (Long id : ids) {
            if (deptMapper.selectCountByParentId(id) > 0) {
                throw exception(DEPT_EXITS_CHILDREN);
            }
        }

        // 批量删除部门
        deptMapper.deleteByIds(ids);
    }

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

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

    @VisibleForTesting
    void validateDeptCodeUnique(Long id, String code) {
        if (StrUtil.isBlank(code)) {
            return;
        }
        DeptDO dept = deptMapper.selectByCode(code);
        if (dept == null) {
            return;
        }
        if (id == null || ObjectUtil.notEqual(dept.getId(), id)) {
            throw exception(DEPT_CODE_DUPLICATE, code);
        }
    }

    @VisibleForTesting
    void validateDeptOrgType(String orgType) {
        if (!DeptOrgTypeEnum.isValid(orgType)) {
            throw exception(DEPT_ORG_TYPE_INVALID, orgType);
        }
    }

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

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

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

    @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
    @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 Set<Long> listDescendantDeptIds(Collection<Long> rootDeptIds) {
        if (CollUtil.isEmpty(rootDeptIds)) {
            return Collections.emptySet();
        }
        Set<Long> result = new HashSet<>(rootDeptIds);
        // 逐个根节点按 path 前缀匹配子孙节点，避免递归查询
        for (Long rootId : rootDeptIds) {
            DeptDO root = deptMapper.selectById(rootId);
            if (root == null || StrUtil.isBlank(root.getPath())) {
                continue;
            }
            List<DeptDO> descendants = deptMapper.selectListByPathPrefix(root.getPath());
            descendants.forEach(d -> result.add(d.getId()));
        }
        return result;
    }

    @Override
    public Set<String> listCodesByIds(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptySet();
        }
        // 复用 getDeptList 已有的空判断与批量查询，过滤 code 为空的记录
        return getDeptList(ids).stream()
                .filter(d -> StrUtil.isNotBlank(d.getCode()))
                .map(DeptDO::getCode)
                .collect(Collectors.toSet());
    }

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

    private void normalizeDeptCreateReq(DeptSaveReqVO reqVO) {
        if (reqVO.getParentId() == null) {
            reqVO.setParentId(DeptDO.PARENT_ID_ROOT);
        }
        if (StrUtil.isBlank(reqVO.getOrgType())) {
            reqVO.setOrgType(DeptOrgTypeEnum.defaultType());
        }
        if (StrUtil.isBlank(reqVO.getCode())) {
            reqVO.setCode(null);
        }
    }

    private void normalizeDeptUpdateReq(DeptSaveReqVO reqVO, DeptDO oldDept) {
        if (reqVO.getParentId() == null) {
            reqVO.setParentId(DeptDO.PARENT_ID_ROOT);
        }
        if (StrUtil.isBlank(reqVO.getOrgType())) {
            reqVO.setOrgType(StrUtil.blankToDefault(oldDept.getOrgType(), DeptOrgTypeEnum.defaultType()));
        }
        if (StrUtil.isBlank(reqVO.getCode())) {
            reqVO.setCode(oldDept.getCode());
        }
    }

    private void fillDeptTreeFields(DeptDO dept, Long parentId) {
        String parentPath = "/";
        int parentLevel = 0;
        if (!DeptDO.PARENT_ID_ROOT.equals(parentId)) {
            DeptDO parentDept = deptMapper.selectById(parentId);
            if (parentDept != null) {
                parentPath = StrUtil.blankToDefault(parentDept.getPath(), "/" + parentDept.getId() + "/");
                parentLevel = ObjectUtil.defaultIfNull(parentDept.getLevel(), 1);
            }
        }
        dept.setPath(parentPath + dept.getId() + "/");
        dept.setLevel(parentLevel + 1);
    }

    private void updateChildDeptTree(Long deptId, String oldPath, Integer oldLevel, String newPath, Integer newLevel) {
        if (StrUtil.equals(oldPath, newPath) && Objects.equals(oldLevel, newLevel)) {
            return;
        }
        List<DeptDO> children = deptMapper.selectListByPathPrefix(oldPath);
        children.removeIf(dept -> Objects.equals(dept.getId(), deptId));
        if (CollUtil.isEmpty(children)) {
            return;
        }
        int levelDiff = ObjectUtil.defaultIfNull(newLevel, 1) - ObjectUtil.defaultIfNull(oldLevel, 1);
        children.forEach(child -> {
            child.setPath(newPath + StrUtil.removePrefix(child.getPath(), oldPath));
            child.setLevel(ObjectUtil.defaultIfNull(child.getLevel(), 1) + levelDiff);
        });
        deptMapper.updateBatch(children);
    }

}
