package com.cf.imes.module.system.service.dept;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.cf.imes.framework.common.enums.CommonStatusEnum;
import com.cf.imes.framework.common.exception.util.ServiceExceptionUtil;
import com.cf.imes.framework.common.util.json.JsonUtils;
import com.cf.imes.framework.common.util.object.BeanUtils;
import com.cf.imes.framework.organ.core.context.OrganContextHolder;
import com.cf.imes.framework.security.core.LoginUser;
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
import com.cf.imes.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
import com.cf.imes.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;
import com.cf.imes.module.system.dal.dataobject.dept.DeptDO;
import com.cf.imes.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import com.cf.imes.module.system.dal.mysql.dept.DeptMapper;
import com.cf.imes.module.system.dal.redis.RedisKeyConstants;
import com.cf.imes.module.system.enums.ErrorCodeConstants;
import com.cf.imes.module.system.service.user.AdminUserService;
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.context.annotation.Lazy;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import jakarta.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.cf.imes.framework.common.util.collection.CollectionUtils.convertSet;
import static com.cf.imes.module.system.dal.redis.RedisKeyConstants.OAUTH2_ACCESS_TOKEN;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.DEPT_USER_OPER_NOT_ALLOW;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PARENT_DEPT_USER_OPER_NOT_ALLOW;

/**
 * 部门 Service 实现类
 *
 * @author 晨丰科技
 */
@Service
@Validated
@Slf4j
public class DeptServiceImpl implements DeptService {

    @Resource
    private DeptMapper deptMapper;

    @Resource
    @Lazy // 延迟，避免循环依赖报错
    private AdminUserService userService;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，因为操作一个部门，涉及到多个缓存
    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(), createReqVO.getOrganId());

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

    @Override
    @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，因为操作一个部门，涉及到多个缓存
    public void updateDept(DeptSaveReqVO updateReqVO) {
        Long deptId = updateReqVO.getId();
        if (updateReqVO.getParentId() == null) {
            updateReqVO.setParentId(DeptDO.PARENT_ID_ROOT);
        }
        // 校验自己存在
        validateDeptExists(deptId);
        // 校验父部门的有效性
        validateParentDept(deptId, updateReqVO.getParentId());
        // 校验部门名的唯一性
        validateDeptNameUnique(deptId, updateReqVO.getParentId(), updateReqVO.getName(), updateReqVO.getOrganId());
        // 校验部门内用户操作
        checkCurrentWhenOperate(deptId);

        // 更新部门
        DeptDO updateObj = BeanUtils.toBean(updateReqVO, DeptDO.class);
        deptMapper.updateById(updateObj);
        if (CommonStatusEnum.DISABLE.getStatus().equals(updateReqVO.getStatus())) {
            // 移除部门下用户的token
            scanAndCompareDeptAndDelToken(String.format(OAUTH2_ACCESS_TOKEN, "*"), deptId);
        }
    }

    @Override
    @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，因为操作一个部门，涉及到多个缓存
    public void deleteDept(Long id) {
        // 校验是否存在
        validateDeptExists(id);
        // 校验是否有子部门
        if (deptMapper.selectCountByParentId(id) > 0) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.DEPT_EXITS_CHILDREN);
        }
        // 校验部门内用户操作
        checkCurrentWhenOperate(id);
        // 校验部门内是否存在用户，存在用户则禁止删除
        if (!userService.getUserListByDeptIds(Collections.singletonList(id)).isEmpty()) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.DEPT_EXISTS_USER);
        }
        // 删除部门
        deptMapper.deleteById(id);
        // 删除关联的用户
        userService.deleteDeptUsers(id);
        // 移除部门下用户的token
        scanAndCompareDeptAndDelToken(String.format(OAUTH2_ACCESS_TOKEN, "*"), id);
    }

    /**
     * 轮训redis token，移除对应部门下的用户的token
     *
     * @param keyPattern
     * @param deptId
     */
    private void scanAndCompareDeptAndDelToken(String keyPattern, Long deptId) {
        // 根据keyPattern scan匹配的redis key
        List<String> matchKeys = new ArrayList<>();
        Cursor<String> cursor = stringRedisTemplate.scan(ScanOptions.scanOptions().match(keyPattern).count(200).build());
        while (cursor.hasNext()) {
            matchKeys.add(cursor.next());
        }
        cursor.close();
        if (CollUtil.isNotEmpty(matchKeys)) {
            for (String key : matchKeys) {
                // 获取key下的用户信息
                OAuth2AccessTokenDO oAuth2AccessTokenDO = JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(key), OAuth2AccessTokenDO.class);
                if (ObjectUtil.equal(deptId, oAuth2AccessTokenDO.getDeptId())) {
                    // 用户id匹配上了删除redis中的token缓存
                    stringRedisTemplate.delete(key);
                    return;
                }
            }
        }
    }

    /**
     * 检查是否操作当前部门
     */
    private void checkCurrentWhenOperate(Long deptId) {
        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
        Long currentDeptId = loginUser.getDeptId();
        if (ObjectUtil.isNotNull(loginUser) && ObjectUtil.equal(deptId, currentDeptId)) {
            throw exception(DEPT_USER_OPER_NOT_ALLOW);
        }
        // 递归查询所有下级，如果当前用户在范围内不允许操作
        List<DeptDO> childDeptList = getChildDeptList(deptId);
        childDeptList.stream().filter(d -> d.getId().equals(currentDeptId)).findAny().ifPresent(deptDO -> {
            throw exception(PARENT_DEPT_USER_OPER_NOT_ALLOW);
        });
    }

    @VisibleForTesting
    void validateDeptExists(Long id) {
        if (id == null) {
            return;
        }
        DeptDO dept = deptMapper.selectById(id);
        if (dept == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.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 ServiceExceptionUtil.exception(ErrorCodeConstants.DEPT_PARENT_ERROR);
        }
        // 2. 父部门不存在
        DeptDO parentDept = deptMapper.selectById(parentId);
        if (parentDept == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.DEPT_PARENT_NOT_EXITS);
        }
        // 2.1 父部门状态校验
        checkParentStatus(parentDept.getStatus(), parentDept.getName());

        for (int i = 0; i < Short.MAX_VALUE; i++) {
            // 3.1 校验环路
            parentId = parentDept.getParentId();
            if (Objects.equals(id, parentId)) {
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.DEPT_PARENT_IS_CHILD);
            }
            // 3.2 继续递归下一级父部门
            if (parentId == null || DeptDO.PARENT_ID_ROOT.equals(parentId)) {
                break;
            }
            parentDept = deptMapper.selectById(parentId);
            if (parentDept == null) {
                break;
            }
            // 3.3 父部门状态校验
            checkParentStatus(parentDept.getStatus(), parentDept.getName());
        }
    }

    /**
     * 校验上级部门是否开启
     *
     * @param status
     * @param parentName
     */
    private void checkParentStatus(Integer status, String parentName) {
        if (!CommonStatusEnum.ENABLE.getStatus().equals(status)) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.PARENT_DEPT_DISABLE, parentName);
        }
    }

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

    @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.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> getChildDeptList(Long id) {
        List<DeptDO> children = new LinkedList<>();
        // 遍历每一层
        Collection<Long> parentIds = Collections.singleton(id);
        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 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 ServiceExceptionUtil.exception(ErrorCodeConstants.DEPT_NOT_FOUND);
            }
            if (!CommonStatusEnum.ENABLE.getStatus().equals(dept.getStatus())) {
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.DEPT_NOT_ENABLE, dept.getName());
            }
        });
    }

    @Override
    public void validDept(Long deptId) {
        DeptDO deptDO = deptMapper.selectById(deptId);
        if (deptDO == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.DEPT_NOT_FOUND);
        }
        if (!CommonStatusEnum.ENABLE.getStatus().equals(deptDO.getStatus())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.DEPT_DISABLE, deptDO.getName());
        }
        // 递归校验上级部门
        validParentDept(deptDO);
    }


    // 用户登陆时部门校验，部门不存在时也可以进行登录
    @Override
    public void validUserLoginDept(Long deptId) {
        DeptDO deptDO = deptMapper.selectById(deptId);
        if (deptDO == null) {
            return;
        }
        if (!CommonStatusEnum.ENABLE.getStatus().equals(deptDO.getStatus())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.DEPT_NOT_ALLOWED_LOGIN, deptDO.getName());
        }
        // 递归校验上级部门
        validParentDept(deptDO);
    }


    /**
     * 递归校验上级部门
     *
     * @param deptDO
     */
    private void validParentDept(DeptDO deptDO) {
        if (ObjectUtil.isNotNull(deptDO) && ObjectUtil.isNotNull(deptDO.getParentId())) {
            Long parentId = deptDO.getParentId();
            DeptDO parentDept = deptMapper.selectById(parentId);
            if (ObjectUtil.equal(parentId, 0L)) {
                // 上级部门id是0代表是机构下最顶级的部门
                return;
            }
            if (parentDept == null) {
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.DEPT_PARENT_NOT_EXITS);
            } else if (!CommonStatusEnum.ENABLE.getStatus().equals(parentDept.getStatus())) {
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.DEPT_DISABLE, parentDept.getName());
            } else {
                validParentDept(parentDept);
            }
        }
    }

    @Override
    public List<DeptDO> removeUnowndDept(List<DeptDO> deptDOS) {
        if (CollUtil.isEmpty(deptDOS)) {
            return deptDOS;
        }

        Map<Long, DeptDO> deptMap = deptDOS.stream()
                .collect(Collectors.toMap(DeptDO::getId, deptDO -> deptDO));
        Map<Long, Boolean> topLevelCache = new HashMap<>();

        Set<DeptDO> resultSet = new LinkedHashSet<>();

        for (DeptDO deptDO : deptDOS) {
            if (isParentDeptTopLevel(deptDO, deptMap, topLevelCache)) {
                resultSet.add(deptDO);
            }
        }

        return new ArrayList<>(resultSet);
    }

    /**
     * 列表内可以找到的父级是否最顶级部门（parentId是0）
     *
     * @param deptDO
     * @param deptMap
     * @param topLevelCache 缓存已计算过的部门是否为最顶级部门
     * @return
     */
    private boolean isParentDeptTopLevel(DeptDO deptDO, Map<Long, DeptDO> deptMap, Map<Long, Boolean> topLevelCache) {
        Long currentId = deptDO.getId();

        while (currentId != 0) {
            DeptDO currentDept = deptMap.get(currentId);
            if (currentDept == null) {
                return false; // 如果找不到上级部门，返回false
            }

            // 检查缓存
            if (topLevelCache.containsKey(currentDept.getId())) {
                return topLevelCache.get(currentDept.getId());
            }

            if (currentDept.getParentId() == 0) {
                topLevelCache.put(currentDept.getId(), true);
                return true; // 找到最顶级部门且parentId为0，返回true
            }

            // 继续找上级
            currentId = currentDept.getParentId();
        }

        topLevelCache.put(deptDO.getId(), false);
        return false; // 默认返回false
    }

    @Override
    public Set<Long> getAllInvalidDeptIds(Long organId) {
        if (ObjectUtils.isNull(organId)) {
            organId = OrganContextHolder.getOrganId();
        }

        // 一次性加载当前 organ 下的所有部门
        List<DeptDO> allDepts = deptMapper.selectList(
                new LambdaQueryWrapper<DeptDO>().eq(DeptDO::getOrganId, organId)
        );

        // 构建 parentId → 子部门列表 的内存映射关系
        Map<Long, List<DeptDO>> deptTree = allDepts.stream()
                .collect(Collectors.groupingBy(DeptDO::getParentId));

        Set<Long> invalidDeptIds = new HashSet<>();
        // 获取所有根部门（parentId=0）
        List<DeptDO> topDepts = deptTree.getOrDefault(0L, Collections.emptyList());
        for (DeptDO topDept : topDepts) {
            collectInvalidDeptIdsOptimized(topDept,
                    topDept.getStatus().equals(CommonStatusEnum.DISABLE.getStatus()),
                    deptTree,
                    invalidDeptIds
            );
        }
        return invalidDeptIds;
    }

    /**
     * 优化后的递归方法（内存操作，无数据库查询）
     */
    private void collectInvalidDeptIdsOptimized(DeptDO currentDept,
                                                boolean parentClosed,
                                                Map<Long, List<DeptDO>> deptTree,
                                                Set<Long> result) {
        boolean currentClosed = parentClosed ||
                currentDept.getStatus().equals(CommonStatusEnum.DISABLE.getStatus());

        if (currentClosed) {
            result.add(currentDept.getId());
        }

        // 直接从内存映射中获取子部门（无需查数据库）
        List<DeptDO> children = deptTree.getOrDefault(currentDept.getId(), Collections.emptyList());
        for (DeptDO child : children) {
            collectInvalidDeptIdsOptimized(child, currentClosed, deptTree, result);
        }
    }
}
