package com.njzscloud.common.security.permission;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.njzscloud.common.security.contant.Constants;
import com.njzscloud.common.security.contant.EndpointAccessModel;
import com.njzscloud.common.security.ex.ForbiddenAccessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/**
 * 权限管理器
 */
@Slf4j
public abstract class PermissionManager {

    private static final ReentrantLock PERMISSION_CACHE_LOCK = new ReentrantLock();
    /**
     * 权限缓存，请求地址——角色编码
     */
    private Map<AntPathRequestMatcher, Collection<ConfigAttribute>> PERMISSION_CACHE;

    private Set<AntPathRequestMatcher> FORBIDDEN_CACHE;

    /**
     * 刷新本地权限缓存
     */
    public final void refresh() {
        try {
            if (log.isDebugEnabled()) log.debug("刷新本地权限缓存已清除");
            PERMISSION_CACHE_LOCK.lock();
            PERMISSION_CACHE = null;
            FORBIDDEN_CACHE = null;
            if (log.isDebugEnabled()) log.debug("本地权限缓存已清除");
            this.load0();
        } finally {
            PERMISSION_CACHE_LOCK.unlock();
        }
    }

    /**
     * 初始化本地权限缓存
     */
    public final void init() {
        // if (log.isDebugEnabled()) log.debug("初始化本地权限缓存");
        if (CollUtil.isEmpty(PERMISSION_CACHE)) {
            try {
                PERMISSION_CACHE_LOCK.lock();
                if (CollUtil.isEmpty(PERMISSION_CACHE)) {
                    this.load0();
                    return;
                }
            } finally {
                PERMISSION_CACHE_LOCK.unlock();
            }
        }
        // if (log.isDebugEnabled()) log.debug("已初始化无需操作");
    }

    /**
     * 加载权限
     */
    private void load0() {
        // if (log.isDebugEnabled()) log.debug("开始加载权限");

        List<RolePermission> rolePermissions = load();

        if (rolePermissions == null) rolePermissions = Collections.emptyList();

        Map<AntPathRequestMatcher, Collection<ConfigAttribute>> permissionMap = new LinkedHashMap<>();

        Set<AntPathRequestMatcher> forbiddenSet = new HashSet<>();

        for (RolePermission rolePermission : rolePermissions) {
            String endpoint = rolePermission.getEndpoint();
            String method = rolePermission.getMethod();
            EndpointAccessModel accessModel = rolePermission.getAccessModel();

            AntPathRequestMatcher pathRequestMatcher = new AntPathRequestMatcher(endpoint, method);
            if (accessModel == EndpointAccessModel.FORBIDDEN) {
                forbiddenSet.add(pathRequestMatcher);
                continue;
            }

            Collection<ConfigAttribute> configAttributes = permissionMap.computeIfAbsent(pathRequestMatcher, it -> new HashSet<>());

            if (accessModel == EndpointAccessModel.ANONYMOUS) {
                configAttributes.add(new SecurityConfig(Constants.ROLE_ANONYMOUS));
                configAttributes.add(new SecurityConfig(Constants.ROLE_AUTHENTICATED));
            } else if (accessModel == EndpointAccessModel.LOGINED) {
                configAttributes.add(new SecurityConfig(Constants.ROLE_AUTHENTICATED));
            } else if (accessModel == EndpointAccessModel.AUTHENTICATED) {
                String role = rolePermission.getRole();
                if (StrUtil.isNotBlank(role)) configAttributes.add(new SecurityConfig(role));
            }
        }

        FORBIDDEN_CACHE = forbiddenSet;
        PERMISSION_CACHE = permissionMap;

        // if (log.isDebugEnabled()) log.debug("本地权限缓存已加载：\n{}", Jackson.toJsonStr(this.getAllRelation()));
    }

    /**
     * 加载权限
     *
     * @return List&lt;RolePermission&gt;
     */
    abstract protected List<RolePermission> load();

    /**
     * 获取当前请求所需要的角色
     *
     * @param request 请求对象
     * @return Collection&lt;ConfigAttribute&gt;
     */
    public final Collection<ConfigAttribute> extractAuthorities(HttpServletRequest request) {
        this.init();
        if (FORBIDDEN_CACHE != null) {
            for (AntPathRequestMatcher antPathRequestMatcher : FORBIDDEN_CACHE) {
                if (antPathRequestMatcher.matches(request)) {
                    throw new ForbiddenAccessException("当前服务已停止使用！");
                }
            }
        }
        if (PERMISSION_CACHE != null) {
            for (Map.Entry<AntPathRequestMatcher, Collection<ConfigAttribute>> entry : PERMISSION_CACHE.entrySet()) {
                if (entry.getKey().matches(request)) {
                    return entry.getValue();
                }
            }
        }
        return CollUtil.empty(Set.class);
    }

    /**
     * 获取所有角色
     *
     * @return 角色列表
     */
    public final Collection<ConfigAttribute> getAll() {
        this.init();
        Set<ConfigAttribute> allAttributes = new HashSet<>();
        if (PERMISSION_CACHE != null) PERMISSION_CACHE.values().forEach(allAttributes::addAll);
        return CollUtil.unmodifiable(allAttributes);
    }

    /**
     * 获取权限的字符表示形式
     *
     * @return Map&lt;String, Set&lt;String&gt;&gt;
     */
    public synchronized final Map<String, Set<String>> getAllRelation() {
        Map<String, Set<String>> map = new HashMap<>();
        if (PERMISSION_CACHE != null) {
            Set<Map.Entry<AntPathRequestMatcher, Collection<ConfigAttribute>>> entries = PERMISSION_CACHE.entrySet();
            for (Map.Entry<AntPathRequestMatcher, Collection<ConfigAttribute>> entry : entries) {
                AntPathRequestMatcher key = entry.getKey();
                Collection<ConfigAttribute> value = entry.getValue();
                Set<String> collect = value.stream().map(ConfigAttribute::getAttribute).collect(Collectors.toSet());
                map.put(key.toString(), collect);
            }
        }
        return MapUtil.unmodifiable(map);
    }
}
