package com.keyware.shandan.bianmu.service;

import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.keyware.shandan.bianmu.entity.DirectoryMembersVo;
import com.keyware.shandan.bianmu.entity.DirectoryVo;
import com.keyware.shandan.bianmu.enums.DirectoryType;
import com.keyware.shandan.common.util.StreamUtil;
import com.keyware.shandan.frame.config.security.SecurityUtil;
import com.keyware.shandan.system.entity.SysOrg;
import com.keyware.shandan.system.entity.SysRole;
import com.keyware.shandan.system.entity.SysUser;
import com.keyware.shandan.system.service.SysOrgService;
import com.keyware.shandan.system.service.SysRoleService;
import com.keyware.shandan.system.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Slf4j
@Service
public class DirPermissionService {
    private final String[] ROOT_KEYS = {"ROOT", "ROOT_THEME", "ROOT_BASIC_DATA"};
    // 目录数据缓存
    private final List<DirectoryVo> directoryList = Collections.synchronizedList(new ArrayList<>());
    // 权限缓存
    private final ConcurrentHashMap<String, Set<String>> permisCache = new ConcurrentHashMap<>();
    // 成员配置缓存
    private final ConcurrentHashMap<String, Set<String>> membersCache = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, List<SysRole>> roleCache = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, SysOrg> orgCache = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, SysUser> userCache = new ConcurrentHashMap<>();


    @Autowired
    private DirectoryService directoryService;
    @Autowired
    private DirectoryMembersService membersService;
    @Autowired
    private SysUserService userService;
    @Autowired
    private SysOrgService orgService;
    @Autowired
    private SysRoleService roleService;

    public Set<String> getByReadPermis(SysUser user) {
        Set<String> permis = permisCache.get(readKey(user));
        if (permis != null) {
            return permis;
        }
        refreshCache();
        Set<String> idSet = permisCache.get(readKey(user));
        idSet.addAll(permisCache.get(writeKey(user)));
        return idSet;
    }

    public Set<String> getByWritePermis(SysUser user) {
        Set<String> permis = permisCache.get(writeKey(user));
        if (permis != null) {
            return permis;
        }
        refreshCache();
        return permisCache.get(writeKey(user));
    }

    public void refreshCache() {
        StopWatch watch = new StopWatch();
        watch.start();
        SysUser user = SecurityUtil.getLoginSysUser();
        initCache(user);
        directoryList.addAll(selectDirectoryList());
        selectOrgList().forEach(org -> orgCache.put(org.getId(), org));
        selectUserList().forEach(u -> userCache.put(u.getUserId(), u));
        putMemberCache(membersService.listByUserId(user.getUserId()));
        putMemberCache(membersService.listByOrgId(user.getOrgId()));
        if (user.getOrg() != null) {
            SysOrg org = user.getOrg();
            List<String> parentsIdList = Arrays.asList(org.getOrgPath().split("\\|"));
            LambdaQueryWrapper<DirectoryMembersVo> query = new LambdaQueryWrapper<>();
            query.in(DirectoryMembersVo::getMemberId, parentsIdList);

            membersService.list(query).stream().filter(DirectoryMembersVo::getApplyChildren).forEach(m -> {
                m.setMemberId(user.getUserId());
                m.setMemberType(DirectoryMembersVo.MemberType.USER);
                m.setId(null);
                putMemberCache(Collections.singletonList(m));
            });


        }
        roleCache.put(user.getUserId(), roleService.getUserRoles(user.getUserId()));

        refreshPermisCache(user);
        watch.stop();
        log.info("刷新用户【{}】的目录权限缓存完成,总耗时：{}", user.getUserName(), watch.getTotalTimeSeconds());
    }

    private void refreshPermisCache(SysUser user) {
        // 如果是超级管理员，则拥有所有权限
        if (isSuperAdmin(user)) {
            Set<String> dirIds = StreamUtil.as(directoryList).map(DirectoryVo::getId).toSet();
            permisCache.put(readKey(user), dirIds);
            permisCache.put(writeKey(user), dirIds);
        } else {
            directoryList.forEach(dir -> {
                if (hasReadPermis(user, dir)) {
                    permisCache.get(readKey(user)).add(dir.getId());
                }
                if (hasWritePermis(user, dir)) {
                    permisCache.get(writeKey(user)).add(dir.getId());
                }
            });
        }
    }

    /**
     * 判断是否有查询权限
     *
     * @param user
     * @param dir
     * @return
     */
    public boolean hasReadPermis(SysUser user, DirectoryVo dir) {
        try {
            if (secretLevelAllow(user, dir)) {
                return isEveryoneRead(dir)
                        || isOwnerSelf(user, dir)
                        || isAllParentRead(user, dir)
                        || isParentRead(user, dir)
                        || isAllChildRead(user, dir)
                        || isChildRead(user, dir)
                        || isMemberRead(user, dir);
            }
        } catch (Exception e) {
            log.error("目录权限校验异常：目录ID：{}，用户ID：{}", dir.getId(), user.getUserId());
        }
        return false;
    }

    /**
     * 判断数据密级
     *
     * @param user
     * @param dir
     * @return
     */
    private boolean secretLevelAllow(SysUser user, DirectoryVo dir) {

        return user.getSecretLevel() >= dir.getSecretLevel();
    }

    /**
     * 判断是否有修改权限
     *
     * @param user
     * @param dir
     * @return
     */
    public boolean hasWritePermis(SysUser user, DirectoryVo dir) {
        try {
            if (secretLevelAllow(user, dir)) {
                return isEveryoneWrite(dir)
                        || isOwnerSelf(user, dir)
                        || isAllParentWrite(user, dir)
                        || isParentWrite(user, dir)
                        || isAllChildWrite(user, dir)
                        || isChildWrite(user, dir)
                        || isMemberWrite(user, dir);
            }
        } catch (Exception e) {
            log.error("目录权限校验异常：目录ID：{}，用户ID：{}", dir.getId(), user.getUserId());
        }
        return false;
    }

    private boolean isAllParentRead(SysUser user, DirectoryVo dir) {
        return dir.getPermisAllParent() != null && dir.getPermisAllParent().contains("read") && isAllParent(user, dir);
    }

    private boolean isAllParentWrite(SysUser user, DirectoryVo dir) {
        return dir.getPermisAllParent() != null && dir.getPermisAllParent().contains("write") && isAllParent(user, dir);
    }

    private boolean isAllParent(SysUser user, DirectoryVo dir) {
        if ("org".equals(dir.getOwnerType())) {
            // 归属者为部门， 当前部门所有用户和上级部门所有用户
            return isDirOwnerOrgUser(user, dir) || isDirOwnerAllParentOrgUser(user, dir);
        } else {
            /*if (isAdmin(userOwner)) {
                // 归属者为部门管理员：所有该部门管理员（如有2个或2个以上个部门管理员，虽然为同级，但依旧可见）以及所有上级部门内的所有用户可见
                return userOwner.getOrgId().equals(user.getOrgId()) && isAdmin(user)
                        || getOrgParentsId(userOwner.getOrgId()).contains(user.getOrgId());
            } else {
                // 归属者为普通用户：自己和部门管理员以及所有上级部门内的所有用户可见

            }*/
            return isDirOwnerOrgAdmin(user, dir)
                    || isDirOwnerAllParentOrgUser(user, dir);
        }
    }

    private boolean isParentRead(SysUser user, DirectoryVo dir) {
        return dir.getPermisParent() != null && dir.getPermisParent().contains("read") && isParent(user, dir);
    }

    private boolean isParentWrite(SysUser user, DirectoryVo dir) {
        return dir.getPermisParent() != null && dir.getPermisParent().contains("write") && isParent(user, dir);
    }

    private boolean isParent(SysUser user, DirectoryVo dir) {
        if ("org".equals(dir.getOwnerType())) {
            // 归属者为部门， 当前部门所有人和直属上级部门所有人
            return isDirOwnerOrgUser(user, dir) || isDirOwnerParentOrgUser(user, dir);
        } else {
            if (isAdmin(userCache.get(dir.getOwnerId()))) {
                // 归属者为部门管理员：所有该部门管理员（如有2个或2个以上个部门管理员，虽然为同级，但依旧可见）和直属上级部门所有人
                return isDirOwnerOrgAdmin(user, dir)
                        || isDirOwnerParentOrgUser(user, dir);
            } else {
                // 归属者为普通用户：自己和部门管理员可见
                return isDirOwnerOrgAdmin(user, dir);
            }
        }
    }

    private boolean isAllChildRead(SysUser user, DirectoryVo dir) {
        return dir.getPermisAllChild() != null && dir.getPermisAllChild().contains("read") && isAllChild(user, dir);
    }

    private boolean isAllChildWrite(SysUser user, DirectoryVo dir) {
        return dir.getPermisAllChild() != null && dir.getPermisAllChild().contains("write") && isAllChild(user, dir);
    }

    private boolean isAllChild(SysUser user, DirectoryVo dir) {
        if ("org".equals(dir.getOwnerType())) {
            // 归属者为部门， 当前部门所有用户和所有下级部门的所有用户可见
            return isDirOwnerOrgUser(user, dir) || isDirOwnerOrgAllChildUser(user, dir);
        } else {
            if (isAdmin(userCache.get(dir.getOwnerId()))) {
                // 归属者为部门管理员：自己部门所有用户（如有2个或2个以上个部门管理员，虽然为同级，但依旧可见）和所有下级部门的所有用户
                return isDirOwnerOrgUser(user, dir)
                        || isDirOwnerOrgAllChildUser(user, dir);
            } else {
                // 归属者为普通用户：自己可见
                return isDirOwnerOrgAllChildUser(user, dir);
            }
        }
        //return isDirOwnerOrgUser(user, dir) || isDirOwnerOrgAllChildUser(user, dir);
    }

    private boolean isChildRead(SysUser user, DirectoryVo dir) {
        return dir.getPermisChild() != null && dir.getPermisChild().contains("read") && isChild(user, dir);
    }

    private boolean isChildWrite(SysUser user, DirectoryVo dir) {
        return dir.getPermisChild() != null && dir.getPermisChild().contains("write") && isChild(user, dir);
    }

    private boolean isChild(SysUser user, DirectoryVo dir) {
        if ("org".equals(dir.getOwnerType())) {
            // 归属者为部门， 当前部门所有人和直属下级部门的所有用户
            return isDirOwnerOrgUser(user, dir) || isDirOwnerOrgChildUser(user, dir);
        } else {
            /*if (isAdmin(userCache.get(dir.getOwnerId()))) {
                // 归属者为部门管理员：自己部门所有用户（如有2个或2个以上个部门管理员，虽然为同级，但依旧可见）可见
                return isDirOwnerOrgUser(user, dir);
            } else {
                // 归属者为普通用户：自己可见
                return isOwnerSelf(user, dir);
            }*/
            return isDirOwnerOrgUser(user, dir);
        }
    }

    /**
     * 任意用户可查看
     *
     * @param dir
     * @return
     */
    private boolean isEveryoneRead(DirectoryVo dir) {
        return dir.getPermisEveryone() != null && dir.getPermisEveryone().contains("read");
    }

    /**
     * 任意用户可修改
     *
     * @param dir
     * @return
     */
    private boolean isEveryoneWrite(DirectoryVo dir) {
        return dir.getPermisEveryone() != null && dir.getPermisEveryone().contains("write");
    }

    /**
     * 当前用户是否是目录归属者本人
     *
     * @param user
     * @param dir
     * @return
     */
    private boolean isOwnerSelf(SysUser user, DirectoryVo dir) {
        if ("org".equals(dir.getOwnerType())) {
            return user.isLeader() && user.getOrgId().equals(dir.getOwnerId());
        }
        return user.getUserId().equals(dir.getOwnerId());
    }

    /**
     * 当前用户是否超级管理员
     *
     * @param user
     * @return
     */
    private boolean isSuperAdmin(SysUser user) {
        return StreamUtil.as(roleCache.get(user.getUserId())).anyMatch(role -> "ROLE_SA".equals(role.getRoleName()));
    }

    /**
     * 当前用户是否部门管理员
     *
     * @param user
     * @return
     */
    private boolean isAdmin(SysUser user) {
        if (!roleCache.containsKey(user.getUserId())) {
            roleCache.put(user.getUserId(), roleService.getUserRoles(user.getUserId()));
        }
        return StreamUtil.as(roleCache.get(user.getUserId())).anyMatch(role -> "ROLE_ADMIN".equals(role.getRoleName()));
    }

    /**
     * 是否为目录归属部门下的用户
     *
     * @return
     */
    private boolean isDirOwnerOrgUser(SysUser user, DirectoryVo dir) {
        return user.getOrgId().equals(getDirOwnerOrg(dir).getId());
    }

    /**
     * 是否为目录归属者任意父级部门用户
     *
     * @param user
     * @param dir
     * @return
     */
    private boolean isDirOwnerAllParentOrgUser(SysUser user, DirectoryVo dir) {
        return getOrgParentsId(getDirOwnerOrg(dir).getId()).contains(user.getOrgId());
    }

    /**
     * 是否为目录归属者的直属上级部门用户
     *
     * @return
     */
    private boolean isDirOwnerParentOrgUser(SysUser user, DirectoryVo dir) {
        return getDirOwnerOrg(dir).getId().equals(user.getOrgId());
    }

    /**
     * 是否为目录归属部门的管理员
     *
     * @param user
     * @param dir
     * @return
     */
    private boolean isDirOwnerOrgAdmin(SysUser user, DirectoryVo dir) {
        return isDirOwnerOrgUser(user, dir) && isAdmin(user);
    }

    /**
     * 是否为目录归属部门任意下级部门用户
     *
     * @param user
     * @param dir
     * @return
     */
    private boolean isDirOwnerOrgAllChildUser(SysUser user, DirectoryVo dir) {
        return getOrg(user.getOrgId()).getOrgPath().contains(getDirOwnerOrg(dir).getId());
    }

    /**
     * 是否为目录归属部门的直属下级部门用户
     *
     * @param user
     * @param dir
     * @return
     */
    private boolean isDirOwnerOrgChildUser(SysUser user, DirectoryVo dir) {
        return getOrg(user.getOrgId()).getOrgParentId().equals(getDirOwnerOrg(dir).getId());
    }

    private boolean isMemberRead(SysUser user, DirectoryVo dir) {
        return membersCache.get(readKey(user)).contains(dir.getId());
    }

    private boolean isMemberWrite(SysUser user, DirectoryVo dir) {
        return membersCache.get(writeKey(user)).contains(dir.getId());
    }


    private void putMemberCache(List<DirectoryMembersVo> membersList) {
        List<String> idList = new ArrayList<>();
        membersList.forEach(member -> {
            idList.clear();
            if (member.getMemberType() == DirectoryMembersVo.MemberType.ORG) {
                idList.addAll(userCache.values().stream().filter(user -> user.getOrgId().equals(member.getMemberId())).map(SysUser::getUserId).collect(Collectors.toList()));
            } else {
                idList.add(member.getMemberId());
            }
            String[] types = member.getPermisType().split(",");
            Arrays.asList(types).forEach(type -> {
                idList.forEach(id -> {
                    String key = memberCacheKey(id, type);
                    membersCache.computeIfAbsent(key, k -> new HashSet<>());
                    membersCache.get(key).add(member.getDirectoryId());
                });
            });
        });
    }

    /**
     * 清空指定用户的权限缓存
     *
     * @param user
     */
    public void initCache(SysUser user) {
        String readKey = readKey(user);
        String writeKey = writeKey(user);
        directoryList.clear();
        membersCache.put(readKey, new HashSet<>());
        membersCache.put(writeKey, new HashSet<>());
        permisCache.put(readKey, new HashSet<>());
        permisCache.put(writeKey, new HashSet<>());
        roleCache.put(user.getUserId(), new ArrayList<>());

        List<String> rootKeys = Arrays.asList(ROOT_KEYS);
        membersCache.get(readKey).addAll(rootKeys);
        membersCache.get(writeKey).addAll(rootKeys);
        permisCache.get(readKey).addAll(rootKeys);
        permisCache.get(writeKey).addAll(rootKeys);
    }

    /**
     * 用户查看权限的缓存的key
     *
     * @param user
     * @return
     */
    private String readKey(SysUser user) {
        return memberCacheKey(user.getUserId(), "read");
    }

    /**
     * 用户修改权限的缓存key
     *
     * @param user
     * @return
     */
    private String writeKey(SysUser user) {
        return memberCacheKey(user.getUserId(), "write");
    }

    /**
     * 返回目录成员的缓存的key
     *
     * @param memberId
     * @param permisType
     * @return
     */
    private String memberCacheKey(String memberId, String permisType) {
        return memberId + ":" + permisType;
    }

    /**
     * 获取部门的所有的上级部门ID
     *
     * @param orgId
     * @return
     */
    private List<String> getOrgParentsId(String orgId) {
        SysOrg org = getOrg(orgId);
        String[] parentIds = org.getOrgPath().split("\\|");
        return Arrays.asList(parentIds);
    }

    /**
     * 获取部门
     *
     * @param orgId
     * @return
     */
    private SysOrg getOrg(String orgId) {
        SysOrg org = orgCache.get(orgId);
        if (org == null) {
            org = orgService.getById(orgId);
            orgCache.put(orgId, org);
        }
        Assert.notNull(org, "部门不存在：" + orgId);
        return org;
    }

    /**
     * 获取用户
     *
     * @param userId
     * @return
     */
    private SysUser getUser(String userId) {
        SysUser user = userCache.get(userId);
        if (user == null) {
            user = userService.getById(userId);
            userCache.put(userId, user);
        }
        Assert.notNull(user, "用户不存在：" + userId);
        return user;
    }

    /**
     * 获取目录的归属部门
     *
     * @param dir
     * @return
     */
    private SysOrg getDirOwnerOrg(DirectoryVo dir) {
        if ("org".equals(dir.getOwnerType())) {
            return getOrg(dir.getOwnerId());
        } else {
            return getOrg(getUser(dir.getOwnerId()).getOrgId());
        }
    }

    private List<DirectoryVo> selectDirectoryList() {
        QueryWrapper<DirectoryVo> wrapper = new QueryWrapper<>();
        wrapper.select("ID", "DIRECTORY_NAME", "OWNER_TYPE", "OWNER_ID", "PERMIS_EVERYONE", "PERMIS_ALL_PARENT", "PERMIS_PARENT", "PERMIS_ALL_CHILD", "PERMIS_CHILD", "SECRET_LEVEL");
        wrapper.in("DIRECTORY_TYPE", DirectoryType.DIRECTORY, DirectoryType.LINK_DIR);
        return directoryService.list(wrapper);
    }

    private List<SysUser> selectUserList() {
        QueryWrapper<SysUser> wrapper = new QueryWrapper<>();
        wrapper.select("USER_ID", "ORG_ID", "USER_NAME", "ORG_LEADER");
        return userService.list(wrapper);
    }

    private List<SysOrg> selectOrgList() {
        QueryWrapper<SysOrg> wrapper = new QueryWrapper<>();
        wrapper.select("ID", "ORG_NAME", "ORG_PARENT_ID", "ORG_PATH");
        return orgService.list(wrapper);
    }
}
