package com.njcn.gateway.security;

import cn.hutool.core.collection.CollectionUtil;
import com.njcn.redis.pojo.enums.RedisKeyEnum;
import com.njcn.redis.utils.RedisUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import reactor.core.publisher.Mono;

import java.util.*;

/**
 * @author hongawen
 * 鉴权管理器
 */
@Slf4j
@Component
@AllArgsConstructor
public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {

    private final RedisUtil redisUtil;

    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
        PathMatcher pathMatcher = new AntPathMatcher();
        ServerHttpRequest request = authorizationContext.getExchange().getRequest();
        //不追究请求方式，如果需追求，需要在数据库中添加请求方式的字段request.getMethodValue()
        String restPath = request.getURI().getPath();
        log.info("请求路径：{}", restPath);
        // 对应跨域的预检请求直接放行
//        if (request.getMethod() == HttpMethod.OPTIONS) {
//            return Mono.just(new AuthorizationDecision(true));
//        }
        // 是否需要鉴权，默认未设置拦截规则需要鉴权
        boolean requireCheck = true;
        //查看是否为公共资源
        List<String> publicFunctions = (List<String>) redisUtil.getObjectByKey(RedisKeyEnum.PUBLIC_FUNCTIONS_KEY.getKey());
        if (CollectionUtil.isNotEmpty(publicFunctions) && publicFunctions.contains(restPath)) {
            requireCheck = false;
        }
        // 从缓存取资源权限角色关系列表
        Object roleFunctionsObject = redisUtil.getObjectByKey(RedisKeyEnum.ROLE_FUNCTION_KEY.getKey());
        if (!Objects.isNull(roleFunctionsObject)) {
            Map<String, List<String>> roleFunctions = (Map<String, List<String>>) roleFunctionsObject;
            //当前资源需要的角色
            Set<String> authorities = new HashSet<>();
            Set<String> roleNames = roleFunctions.keySet();
            roleNames.forEach(roleName -> {
                List<String> functionPaths = roleFunctions.get(roleName);
                if (CollectionUtil.isNotEmpty(functionPaths) && functionPaths.contains(restPath)) {
                    authorities.add(roleName);
                }
            });
            boolean finalRequireCheck = requireCheck;
            return mono
                    .filter(Authentication::isAuthenticated)
                    .flatMapIterable(Authentication::getAuthorities)
                    .map(GrantedAuthority::getAuthority)
                    .any(roleId -> {
                        if (!finalRequireCheck || roleId.equals("ROLE_root")) {
                            return true;
                        } else {
                            return authorities.contains(roleId);
                        }
                    })
                    .map(AuthorizationDecision::new)
                    .defaultIfEmpty(new AuthorizationDecision(false));
        }
        return Mono.just(new AuthorizationDecision(true));
    }

}
