package com.njcn.rdms.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.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.njcn.rdms.framework.common.enums.CommonStatusEnum;
import com.njcn.rdms.framework.common.util.collection.CollectionUtils;
import com.njcn.rdms.module.system.dal.dataobject.permission.MenuDO;
import com.njcn.rdms.module.system.dal.dataobject.permission.RoleDO;
import com.njcn.rdms.module.system.dal.dataobject.permission.RoleMenuDO;
import com.njcn.rdms.module.system.dal.dataobject.permission.UserRoleDO;
import com.njcn.rdms.module.system.dal.mysql.permission.RoleMenuMapper;
import com.njcn.rdms.module.system.dal.mysql.permission.UserRoleMapper;
import com.njcn.rdms.module.system.dal.redis.RedisKeyConstants;
import com.njcn.rdms.module.system.enums.permission.PermissionScopeTypeEnum;
import com.njcn.rdms.module.system.service.user.AdminUserService;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

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.ROLE_IS_DISABLE;
import static com.njcn.rdms.module.system.enums.ErrorCodeConstants.ROLE_NOT_EXISTS;

/**
 * 权限 Service 实现类
 *
 * @author hongawen
 */
@Service
@Slf4j
public class PermissionServiceImpl implements PermissionService {

    private static final String GLOBAL_SCOPE_TYPE = PermissionScopeTypeEnum.GLOBAL.getScopeType();
    private static final String GLOBAL_OBJECT_TYPE = PermissionScopeTypeEnum.GLOBAL_OBJECT_TYPE;

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

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

    @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,
                GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
        // 采用严格模式，如果权限找不到对应的 Menu 的话，也认为没有权限
        if (CollUtil.isEmpty(menuIds)) {
            return false;
        }
        List<MenuDO> menus = getEnablePermissionMenus(menuIds, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
        if (CollUtil.isEmpty(menus)) {
            return false;
        }

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

    /**
     * 加载权限菜单自身及其父链后，再统一过滤禁用节点，避免仅查询按钮节点时误判父菜单缺失。
     */
    private List<MenuDO> getEnablePermissionMenus(Collection<Long> menuIds, String scopeType, String objectType) {
        Set<Long> targetMenuIds = new HashSet<>(menuIds);
        Map<Long, MenuDO> menuMap = new LinkedHashMap<>();
        Set<Long> currentIds = new HashSet<>(menuIds);
        while (CollUtil.isNotEmpty(currentIds)) {
            List<MenuDO> currentMenus = menuService.getMenuList(currentIds, scopeType, objectType);
            if (CollUtil.isEmpty(currentMenus)) {
                break;
            }
            currentMenus.forEach(menu -> menuMap.put(menu.getId(), menu));
            Set<Long> parentIds = convertSet(currentMenus, MenuDO::getParentId);
            parentIds.remove(MenuDO.ID_ROOT);
            parentIds.removeIf(menuMap::containsKey);
            currentIds = parentIds;
        }
        List<MenuDO> enabledMenus = menuService.filterDisableMenus(new ArrayList<>(menuMap.values()));
        enabledMenus.removeIf(menu -> !targetMenuIds.contains(menu.getId()));
        return enabledMenus;
    }

    /**
     * 为已选菜单补齐父链，避免只授权子菜单或按钮时，权限树缺少上级节点。
     */
    private Set<Long> expandMenuIdsWithAncestors(Collection<Long> menuIds, String scopeType, String objectType) {
        Set<Long> results = new LinkedHashSet<>(menuIds);
        menuIds.forEach(menuId -> {
            MenuDO menu = getMenu(menuId, scopeType, objectType);
            while (menu != null && !MenuDO.ID_ROOT.equals(menu.getParentId())) {
                if (!results.add(menu.getParentId())) {
                    break;
                }
                menu = getMenu(menu.getParentId(), scopeType, objectType);
            }
        });
        return results;
    }

    @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 保证本地事务，以及数据源的切换
    @Caching(evict = {
            @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST,
            allEntries = true),
            @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，主要一次更新涉及到的 menuIds 较多，反倒批量会更快
    })
    public void assignRoleMenu(Long roleId, Set<Long> menuIds) {
        RoleDO role = getEnabledRole(roleId);
        menuService.validateMenuList(menuIds, role.getScopeType(), role.getObjectType());
        // 获得角色拥有菜单编号
        Set<Long> dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId);
        // 计算新增和删除的菜单编号
        Set<Long> menuIdList = expandMenuIdsWithAncestors(CollUtil.emptyIfNull(menuIds),
                role.getScopeType(), role.getObjectType());
        Collection<Long> createMenuIds = CollUtil.subtract(menuIdList, dbMenuIds);
        Collection<Long> deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIdList);
        // 执行新增和删除。对于已经授权的菜单，不用做任何处理
        if (CollUtil.isNotEmpty(createMenuIds)) {
            roleMenuMapper.insertBatch(CollectionUtils.convertList(createMenuIds, menuId -> {
                RoleMenuDO entity = new RoleMenuDO();
                entity.setRoleId(roleId);
                entity.setMenuId(menuId);
                return entity;
            }));
        }
        if (CollUtil.isNotEmpty(deleteMenuIds)) {
            roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds);
        }
    }

    @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(Long roleId) {
        RoleDO role = getEnabledRole(roleId);
        return getRoleMenuListByRoleId(Collections.singleton(roleId), role.getScopeType(), role.getObjectType());
    }

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

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

        // 统一按角色实际授权返回当前仍然有效的菜单，并补齐其父链
        Set<Long> scopedRoleIds = convertSet(roleService.getRoleList(roleIds, scopeType, objectType).stream()
                .filter(role -> CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus()))
                .toList(), RoleDO::getId);
        if (CollUtil.isEmpty(scopedRoleIds)) {
            return Collections.emptySet();
        }
        Set<Long> menuIds = convertSet(roleMenuMapper.selectListByRoleId(scopedRoleIds), RoleMenuDO::getMenuId);
        List<MenuDO> menus = menuService.filterDisableMenus(menuService.getMenuList(menuIds, scopeType, objectType));
        return expandMenuIdsWithAncestors(convertSet(menus, MenuDO::getId), scopeType, objectType);
    }

    @Override
    public List<MenuDO> getScopedMenusByRoleId(Long roleId, String scopeType, String objectType) {
        RoleDO role = getEnabledScopedRole(roleId, scopeType, objectType);
        if (role == null) {
            return Collections.emptyList();
        }
        Set<Long> menuIds = getRoleMenuListByRoleId(Collections.singleton(roleId), scopeType, objectType);
        if (CollUtil.isEmpty(menuIds)) {
            return Collections.emptyList();
        }
        return menuService.filterDisableMenus(menuService.getMenuList(menuIds, scopeType, objectType));
    }

    @Override
    public Set<String> getScopedPermissionsByRoleId(Long roleId, String scopeType, String objectType) {
        List<MenuDO> menus = getScopedMenusByRoleId(roleId, scopeType, objectType);
        if (CollUtil.isEmpty(menus)) {
            return Collections.emptySet();
        }
        Set<String> permissions = new LinkedHashSet<>();
        menus.forEach(menu -> {
            if (StrUtil.isNotBlank(menu.getPermission())) {
                permissions.add(menu.getPermission());
            }
        });
        return permissions;
    }

    @Override
    @Cacheable(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
    public Set<Long> getMenuRoleIdListByMenuIdFromCache(Long menuId) {
        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) {
        userService.validateUserList(Collections.singleton(userId));
        roleService.validateRoleList(roleIds, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
        // 获得角色拥有角色编号
        Set<Long> dbRoleIds = convertSet(userRoleMapper.selectListByUserId(userId),
                UserRoleDO::getRoleId);
        Set<Long> dbGlobalRoleIds = convertSet(
                roleService.getRoleList(dbRoleIds, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE), RoleDO::getId);
        // 计算新增和删除的角色编号
        Set<Long> roleIdList = CollUtil.emptyIfNull(roleIds);
        Collection<Long> createRoleIds = CollUtil.subtract(roleIdList, dbGlobalRoleIds);
        Collection<Long> deleteRoleIds = CollUtil.subtract(dbGlobalRoleIds, roleIdList);
        // 执行新增和删除。对于已经授权的角色，不用做任何处理
        if (!CollectionUtil.isEmpty(createRoleIds)) {
            userRoleMapper.insertBatch(CollectionUtils.convertList(createRoleIds, roleId -> {
                UserRoleDO entity = new UserRoleDO();
                entity.setUserId(userId);
                entity.setRoleId(roleId);
                return entity;
            }));
        }
        if (!CollectionUtil.isEmpty(deleteRoleIds)) {
            userRoleMapper.deleteListByUserIdAndRoleIdIds(userId, deleteRoleIds);
        }
    }

    @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) {
        Set<Long> roleIds = getRawUserRoleIdListByUserId(userId);
        List<RoleDO> roles = roleService.getRoleList(roleIds, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
        roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus()));
        return convertSet(roles, RoleDO::getId);
    }

    @Override
    @Cacheable(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
    public Set<Long> getUserRoleIdListByUserIdFromCache(Long userId) {
        Set<Long> roleIds = getRawUserRoleIdListByUserId(userId);
        return convertSet(roleService.getRoleList(roleIds, GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE), RoleDO::getId);
    }

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

    /**
     * 获得自身的代理对象，解决 AOP 生效问题
     *
     * @return 自己
     */
    private MenuDO getMenu(Long menuId, String scopeType, String objectType) {
        List<MenuDO> menus = menuService.getMenuList(Collections.singleton(menuId), scopeType, objectType);
        return CollUtil.isEmpty(menus) ? null : menus.get(0);
    }

    private RoleDO getEnabledRole(Long roleId) {
        RoleDO role = roleService.getRole(roleId);
        if (role == null) {
            throw exception(ROLE_NOT_EXISTS);
        }
        if (!CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())) {
            throw exception(ROLE_IS_DISABLE, role.getName());
        }
        return role;
    }

    /**
     * scoped 权限查询只消费当前仍然有效的对象角色；缺失、作用域不匹配或已禁用时都返回 null，
     * 由上层统一收口为空菜单/空权限结果。
     */
    private RoleDO getEnabledScopedRole(Long roleId, String scopeType, String objectType) {
        RoleDO role = roleService.getRole(roleId, scopeType, objectType);
        if (role == null || !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())) {
            return null;
        }
        return role;
    }

    private PermissionServiceImpl getSelf() {
        return SpringUtil.getBean(getClass());
    }

    /**
     * 获得用户已分配的原始角色编号集合，不做启停状态过滤，供缓存与底层鉴权链路复用。
     */
    private Set<Long> getRawUserRoleIdListByUserId(Long userId) {
        return convertSet(userRoleMapper.selectListByUserId(userId), UserRoleDO::getRoleId);
    }

}
