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

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.cf.imes.framework.common.enums.CommonStatusEnum;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.common.util.collection.CollectionUtils;
import com.cf.imes.framework.datapermission.core.annotation.DataPermission;
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cf.imes.framework.organ.core.aop.OrganIgnore;
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.api.permission.dto.DeptDataPermissionRespDTO;
import com.cf.imes.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleUserReqVO;
import com.cf.imes.module.system.controller.admin.permission.vo.permission.PermissionAssignUserRoleReqVO;
import com.cf.imes.module.system.dal.dataobject.permission.MenuDO;
import com.cf.imes.module.system.dal.dataobject.permission.RoleDO;
import com.cf.imes.module.system.dal.dataobject.permission.RoleMenuDO;
import com.cf.imes.module.system.dal.dataobject.permission.UserRoleDO;
import com.cf.imes.module.system.dal.mysql.permission.RoleMenuMapper;
import com.cf.imes.module.system.dal.mysql.permission.UserRoleMapper;
import com.cf.imes.module.system.dal.redis.RedisKeyConstants;
import com.cf.imes.module.system.dal.redis.RedisRefreshChannelTopicConstants;
import com.cf.imes.module.system.enums.ErrorCodeConstants;
import com.cf.imes.module.system.enums.permission.DataScopeEnum;
import com.cf.imes.module.system.service.dept.DeptService;
import com.cf.imes.module.system.service.user.AdminUserService;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Suppliers;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.function.Supplier;
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.framework.common.util.json.JsonUtils.toJsonString;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.ROLE_ME_ERROR;
import static com.cf.imes.module.system.service.organ.OrganServiceImpl.ORGAN_ADMIN_ROLE_ID;
import static com.cf.imes.module.system.service.organ.OrganServiceImpl.ORGAN_STAFF_ROLE_ID;

/**
 * 权限 Service 实现类
 *
 * @author 晨丰科技
 */
@Service
@Slf4j
public class PermissionServiceImpl implements PermissionService {

    @Resource
    private RoleMenuMapper roleMenuMapper;
    @Resource
    private UserRoleMapper userRoleMapper;

    @Resource
    private RoleService roleService;
    @Resource
    private MenuService menuService;
    @Resource
    private DeptService deptService;
    @Resource
    private AdminUserService userService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public boolean hasAnyPermissions(Long userId, String... permissions) {
        // 如果为空，说明已经有权限
        if (ArrayUtil.isEmpty(permissions)) {
            return true;
        }

        // 获得当前登录的角色。如果为空，说明没有权限
        List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);
        if (CollUtil.isEmpty(roles)) {
            return false;
        }

        // 情况一：遍历判断每个权限，如果有一满足，说明有权限
        for (String permission : permissions) {
            if (hasAnyPermission(roles, permission)) {
                return true;
            }
        }

        // 情况二：如果是超管，也说明有权限
        return roleService.hasAnySuperAdmin(convertSet(roles, RoleDO::getId));
    }

    /**
     * 判断指定角色，是否拥有该 permission 权限
     *
     * @param roles 指定角色数组
     * @param permission 权限标识
     * @return 是否拥有
     */
    private boolean hasAnyPermission(List<RoleDO> roles, String permission) {
        List<Long> menuIds = menuService.getMenuIdListByPermissionFromCache(permission);
        // 采用严格模式，如果权限找不到对应的 Menu 的话，也认为没有权限
        if (CollUtil.isEmpty(menuIds)) {
            return false;
        }

        // 判断是否有权限
        Set<Long> roleIds = convertSet(roles, RoleDO::getId);
        for (Long menuId : menuIds) {
            // 获得拥有该菜单的角色编号集合
            //Set<Long> menuRoleIds = getSelf().getMenuRoleIdListByMenuIdFromCache(menuId);
            Set<Long> menuRoleIds = getSelf().getMenuRoleIdListByMenuIdFromCache1(menuId);
            // 如果有交集，说明有权限
            if (CollUtil.containsAny(menuRoleIds, roleIds)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean hasAnyRoles(Long userId, String... roles) {
        // 如果为空，说明已经有权限
        if (ArrayUtil.isEmpty(roles)) {
            return true;
        }

        // 获得当前登录的角色。如果为空，说明没有权限
        List<RoleDO> roleList = getEnableUserRoleListByUserIdFromCache(userId);
        if (CollUtil.isEmpty(roleList)) {
            return false;
        }

        // 判断是否有角色
        Set<String> userRoles = convertSet(roleList, RoleDO::getCode);
        return CollUtil.containsAny(userRoles, Sets.newHashSet(roles));
    }

    // ========== 角色-菜单的相关方法  ==========

    @Override
    @DSTransactional // 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换
    @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，主要一次更新涉及到的 menuIds 较多，反倒批量会更快
    public void assignRoleMenu(Long roleId, Set<Long> menuIds, Long organId) {
        // 获得角色拥有菜单编号
        Set<Long> dbMenuIds = null;
        if (Objects.equals(roleId, ORGAN_ADMIN_ROLE_ID) || Objects.equals(roleId, ORGAN_STAFF_ROLE_ID)) {
            dbMenuIds = convertSet(roleMenuMapper.selectListByRoleIdWithOrganAdmin(List.of(roleId)), RoleMenuDO::getMenuId);
        }else {
            dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId);
        }

        // 计算新增和删除的菜单编号
        Set<Long> menuIdList = CollUtil.emptyIfNull(menuIds);
        Collection<Long> createMenuIds = CollUtil.subtract(menuIdList, dbMenuIds);
        Collection<Long> deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIdList);
        // 执行新增和删除。对于已经授权的菜单，不用做任何处理
        if (CollUtil.isNotEmpty(createMenuIds)) {
            Function<Long, RoleMenuDO> longRoleMenuDOFunction = menuId -> {
                RoleMenuDO entity = new RoleMenuDO();
                entity.setRoleId(roleId);
                entity.setMenuId(menuId);
                if (Objects.equals(roleId, ORGAN_ADMIN_ROLE_ID) || Objects.equals(roleId, ORGAN_STAFF_ROLE_ID)) {
                    entity.setOrganId(0L);
                }else {
                    entity.setOrganId(organId);
                }
                return entity;
            };
            roleMenuMapper.insertBatch(CollectionUtils.convertList(createMenuIds, longRoleMenuDOFunction));
        }
        if (CollUtil.isNotEmpty(deleteMenuIds)) {
            roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds);
        }

        // 角色菜单权限发生修改，通知刷新本地缓存
        if (CollectionUtil.isNotEmpty(createMenuIds) || CollectionUtil.isNotEmpty(deleteMenuIds)) {
            CompletableFuture.runAsync(() -> {
                redisTemplate.convertAndSend(RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, "");
            }).exceptionally(e -> {
                log.error("redis message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, e.getMessage());
                return null;
            });
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Caching(evict = {
            @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST,
                    allEntries = true), // allEntries 清空所有缓存，此处无法方便获得 roleId 对应的 menu 缓存们
            @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST,
                    allEntries = true) // allEntries 清空所有缓存，此处无法方便获得 roleId 对应的 user 缓存们
    })
    public void processRoleDeleted(Long roleId) {
        // 标记删除 UserRole
        userRoleMapper.deleteListByRoleId(roleId);
        // 标记删除 RoleMenu
        roleMenuMapper.deleteListByRoleId(roleId);
    }

    @Override
    @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
    public void processMenuDeleted(Long menuId) {
        roleMenuMapper.deleteListByMenuId(menuId);
    }

    @Override
    public Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds) {
        if (CollUtil.isEmpty(roleIds)) {
            return Collections.emptySet();
        }

        // 如果是管理员的情况下，获取全部菜单编号
        if (roleService.hasAnySuperAdmin(roleIds)) {
            return convertSet(menuService.getMenuList(), MenuDO::getId);
        }
        // 如果是非管理员的情况下，获得拥有的菜单编号
        List<RoleMenuDO> from = null;
        if(roleIds.contains(ORGAN_ADMIN_ROLE_ID) || roleIds.contains(ORGAN_STAFF_ROLE_ID)) {
            from = roleMenuMapper.selectListByRoleIdWithOrganAdmin(roleIds);
        }else {
            from = roleMenuMapper.selectListByRoleId(roleIds);
        }
        return convertSet(from, RoleMenuDO::getMenuId);
    }


    @Override
    @OrganIgnore
    public Set<Long> getRoleMenuListByRoleId2(Set<Long> roleIds) {
        if (CollUtil.isEmpty(roleIds)) {
            return Collections.emptySet();
        }

        // 如果是管理员的情况下，获取全部菜单编号
        if (roleService.hasAnySuperAdmin(roleIds)) {
            return convertSet(menuService.getMenuList(), MenuDO::getId);
        }
        // 如果是非管理员的情况下，获得拥有的菜单编号
        List<RoleMenuDO> from = roleMenuMapper.selectListByRoleId(roleIds);
        return convertSet(from, RoleMenuDO::getMenuId);
    }

    @Override
    @DSTransactional
    @Deprecated //todo 重写授权角色下的用户，后续计划删除
    public void bathAssignUserRole(List<PermissionAssignUserRoleReqVO> listReqVO) {
        Long contextOrganId = OrganContextHolder.getOrganId();
        listReqVO.forEach(e->{
            Long organId = e.getOrganId() ==null ? contextOrganId : e.getOrganId();
            assignUserRole(e.getUserId(), e.getRoleIds(), organId);
        });
    }

    @Override
    @DSTransactional // 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换
    @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
    public void batchAssignRoleUsers(PermissionAssignRoleUserReqVO reqVO) {
        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
        assert loginUser != null;
        Boolean isSupAdmin = loginUser.getIsSupAdmin();
        Set<Long> userIds = reqVO.getUserIds();
        // 组织id
        Long organId = reqVO.getOrganId();
        // 超管不传组织id则操作本机构角色
        // 非超管不传组织id不允许操作
        if (isSupAdmin) {
            organId = Optional.ofNullable(organId).orElseGet(loginUser::getOrganId);
        } else {
            if (organId == null) {
                throw new ServiceException(ErrorCodeConstants.ROLE_NOT_SUPERADMIN_NO_ORGAN_ID_OPER_ERROR);
            }
        }

        Long roleId = reqVO.getRoleId();
        // 当前用户不能操作含有自己的角色
        if (CollectionUtil.isNotEmpty(userIds)) {
            Long incloudSelf = userIds.stream().filter(userId -> userId.equals(loginUser.getId())).findAny().orElse(null);
            if (incloudSelf != null) {
                throw new ServiceException(ROLE_ME_ERROR);
            }
        }

        // 获得角色下的角色用户列表
        Set<Long> roleUserIds = convertSet(userRoleMapper.selectList(new LambdaQueryWrapperX<UserRoleDO>()
                .eqIfPresent(UserRoleDO::getOrganId, organId)
                .eq(UserRoleDO::getRoleId, roleId)
                .select(UserRoleDO::getUserId)), UserRoleDO::getUserId);

        // 筛选要新增的userRole：前端有，数据库没有
        Collection<Long> createRoleUserIds = CollUtil.subtract(userIds, roleUserIds);
        if (!CollectionUtil.isEmpty(createRoleUserIds)) {
            Long finalOrganId = organId;
            userRoleMapper.insertBatch(CollectionUtils.convertList(createRoleUserIds, userId -> {
                UserRoleDO entity = new UserRoleDO();
                entity.setUserId(userId).setRoleId(roleId).setOrganId(finalOrganId);
                return entity;
            }));
        }

        // 筛选要删除的userRole：前端没有，数据库有
        Collection<Long> deleteRoleUserIds = CollUtil.subtract(roleUserIds, userIds);
        if (!CollectionUtil.isEmpty(deleteRoleUserIds)) {
            userRoleMapper.deleteListByRoleIdAndUserIds(roleId, deleteRoleUserIds);
        }

        // 角色菜单权限发生修改，通知刷新本地缓存
        if (CollectionUtil.isNotEmpty(createRoleUserIds) || CollectionUtil.isNotEmpty(deleteRoleUserIds)) {
            CompletableFuture.runAsync(() -> {
                redisTemplate.convertAndSend(RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, "");
            }).exceptionally(e -> {
                log.error("redis message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, e.getMessage());
                return null;
            });
        }
    }

    @Override
    public Set<Long> getListRoleUsers(Long roleId, Long organId) {
        // 超管查看时没有选择机构则查看本机构
        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
        assert loginUser != null;
        if (loginUser.getIsSupAdmin() && organId == null) {
            organId = loginUser.getOrganId();
        }
        List<UserRoleDO> userRoleDOS = userRoleMapper.selectList(new LambdaQueryWrapperX<UserRoleDO>()
                .eqIfPresent(UserRoleDO::getOrganId, organId)
                .eq(UserRoleDO::getRoleId, roleId)
                .select(UserRoleDO::getUserId)
        );
        if(CollUtil.isEmpty(userRoleDOS)) {
            return new HashSet<>();
        }
        return userRoleDOS.stream().map(UserRoleDO::getUserId).collect(Collectors.toSet());

    }

    @Override
    @Cacheable(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
    public Set<Long> getMenuRoleIdListByMenuIdFromCache(Long menuId) {
        return convertSet(roleMenuMapper.selectListByMenuId(menuId), RoleMenuDO::getRoleId);
    }

    @Override
    @Cacheable(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
    @OrganIgnore
    public Set<Long> getMenuRoleIdListByMenuIdFromCache1(Long menuId) {
       //todo 这里应该过滤组织id为0的和自己的
        List<RoleMenuDO> roleMenuDOS = roleMenuMapper.selectList(new LambdaQueryWrapperX<RoleMenuDO>()
                //.in(RoleMenuDO::getOrganId, 0l, organId)
                .eq(RoleMenuDO::getMenuId, menuId)
        );
        return convertSet(roleMenuDOS, RoleMenuDO::getRoleId);
       // return convertSet(roleMenuMapper.selectListByMenuId(menuId), RoleMenuDO::getRoleId);
    }

    // ========== 用户-角色的相关方法  ==========

    @Override
    @DSTransactional // 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换
    @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
    public void assignUserRole(Long userId, Set<Long> roleIds, Long organId) {
        if(Objects.equals(userId, SecurityFrameworkUtils.getLoginUserId())) {
            throw exception(ROLE_ME_ERROR);
        }
        // 获得用户拥有角色编号
        Set<Long> dbRoleIds = convertSet(userRoleMapper.selectListByUserId(userId),
                UserRoleDO::getRoleId);
        // 计算新增和删除的角色编号
        Set<Long> roleIdList = CollUtil.emptyIfNull(roleIds);
        Collection<Long> createRoleIds = CollUtil.subtract(roleIdList, dbRoleIds);
        Collection<Long> deleteMenuIds = CollUtil.subtract(dbRoleIds, roleIdList);
        // 执行新增和删除。对于已经授权的角色，不用做任何处理
        if (!CollectionUtil.isEmpty(createRoleIds)) {
            userRoleMapper.insertBatch(CollectionUtils.convertList(createRoleIds, roleId -> {
                UserRoleDO entity = new UserRoleDO();
                entity.setUserId(userId);
                entity.setRoleId(roleId);
                entity.setOrganId(organId);
                return entity;
            }));
        }
        if (!CollectionUtil.isEmpty(deleteMenuIds)) {
            userRoleMapper.deleteListByUserIdAndRoleIdIds(userId, deleteMenuIds);
        }

        // 角色菜单权限发生修改，通知刷新本地缓存
        if (CollectionUtil.isNotEmpty(createRoleIds) || CollectionUtil.isNotEmpty(deleteMenuIds)) {
            CompletableFuture.runAsync(() -> {
                redisTemplate.convertAndSend(RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, "");
            }).exceptionally(e -> {
                log.error("redis message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, e.getMessage());
                return null;
            });
        }
    }

    @Override
    @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
    public void processUserDeleted(Long userId) {
        userRoleMapper.deleteListByUserId(userId);
    }

    @Override
    public Set<Long> getUserRoleIdListByUserId(Long userId) {
        return convertSet(userRoleMapper.selectListByUserId(userId), UserRoleDO::getRoleId);
    }

    @Override
    @Cacheable(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
    public Set<Long> getUserRoleIdListByUserIdFromCache(Long userId) {
        return getUserRoleIdListByUserId(userId);
    }

    @Override
    public Set<Long> getUserRoleIdListByRoleId(Collection<Long> roleIds) {
        return convertSet(userRoleMapper.selectListByRoleIds(roleIds), UserRoleDO::getUserId);
    }

    /**
     * 获得用户拥有的角色，并且这些角色是开启状态的
     *
     * @param userId 用户编号
     * @return 用户拥有的角色
     */
    @VisibleForTesting
    List<RoleDO> getEnableUserRoleListByUserIdFromCache(Long userId) {
        // 获得用户拥有的角色编号
        Set<Long> roleIds = getSelf().getUserRoleIdListByUserIdFromCache(userId);
        // 获得角色数组，并移除被禁用的
        List<RoleDO> roles = roleService.getRoleListFromCache(roleIds);
        roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus()));
        return roles;
    }

    // ========== 用户-部门的相关方法  ==========

    @Override
    public void assignRoleDataScope(Long roleId, Integer dataScope, Set<Long> dataScopeDeptIds) {
        roleService.updateRoleDataScope(roleId, dataScope, dataScopeDeptIds);
    }

    @Override
    @DataPermission(enable = false) // 关闭数据权限，不然就会出现递归获取数据权限的问题
    public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) {
        // 获得用户的角色
        List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);

        // 如果角色为空，则只能查看自己
        DeptDataPermissionRespDTO result = new DeptDataPermissionRespDTO();
        if (CollUtil.isEmpty(roles)) {
            result.setSelf(true);
            return result;
        }

        // 获得用户的部门编号的缓存，通过 Guava 的 Suppliers 惰性求值，即有且仅有第一次发起 DB 的查询
        Supplier<Long> userDeptId = Suppliers.memoize(() -> userService.getUser(userId).getDeptId());
        // 遍历每个角色，计算
        for (RoleDO role : roles) {
            // 为空时，跳过
            if (role.getDataScope() == null) {
                continue;
            }
            // 情况一，ALL
            if (Objects.equals(role.getDataScope(), DataScopeEnum.ALL.getScope())) {
                result.setAll(true);
                continue;
            }
            // 情况二，DEPT_CUSTOM
            if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_CUSTOM.getScope())) {
                CollUtil.addAll(result.getDeptIds(), role.getDataScopeDeptIds());
                // 自定义可见部门时，保证可以看到自己所在的部门。否则，一些场景下可能会有问题。
                // 例如说，登录时，基于 t_user 的 username 查询会可能被 dept_id 过滤掉
                CollUtil.addAll(result.getDeptIds(), userDeptId.get());
                continue;
            }
            // 情况三，DEPT_ONLY
            if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_ONLY.getScope())) {
                CollectionUtils.addIfNotNull(result.getDeptIds(), userDeptId.get());
                continue;
            }
            // 情况四，DEPT_DEPT_AND_CHILD
            if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) {
                CollUtil.addAll(result.getDeptIds(), deptService.getChildDeptIdListFromCache(userDeptId.get()));
                // 添加本身部门编号
                CollUtil.addAll(result.getDeptIds(), userDeptId.get());
                continue;
            }
            // 情况五，SELF
            if (Objects.equals(role.getDataScope(), DataScopeEnum.SELF.getScope())) {
                result.setSelf(true);
                continue;
            }
            // 未知情况，error log 即可
            log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", userId, toJsonString(result));
        }
        return result;
    }


    /**
     * 获得自身的代理对象，解决 AOP 生效问题
     *
     * @return 自己
     */
    private PermissionServiceImpl getSelf() {
        return SpringUtil.getBean(getClass());
    }

}
