package com.diagbot.config;

import com.diagbot.util.DateUtil;
import com.diagbot.util.RedisUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.security.core.Authentication;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.security.jwt.crypto.sign.SignatureVerifier;
import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.common.util.JsonParser;
import org.springframework.security.oauth2.common.util.JsonParserFactory;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.approval.Approval;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtClaimsSetVerifier;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.approval.Approval.ApprovalStatus;
import org.springframework.util.FileCopyUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;

/**
 * @Description:token续签
 * @Author songxl
 * @Date 2021/12/17
 */
public class MyJwtTokenStore extends JwtTokenStore {
    private JwtAccessTokenConverter jwtTokenEnhancer;
    private ApprovalStore approvalStore;
    private JsonParser objectMapper = JsonParserFactory.create();
    private JwtClaimsSetVerifier jwtClaimsSetVerifier = new NoOpJwtClaimsSetVerifier();
    private SignatureVerifier verifier;
    private RedisUtils redisUtils;
    public MyJwtTokenStore(JwtAccessTokenConverter jwtTokenEnhancer,RedisUtils redisUtils) {
        super(jwtTokenEnhancer);
        verifier = createVerifier();
        this.jwtTokenEnhancer = jwtTokenEnhancer;
        this.redisUtils = redisUtils;
    }

    private SignatureVerifier createVerifier() {
        Resource resource = new ClassPathResource("public.cert");
        String publicKey;
        try {
            publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return new RsaVerifier(publicKey);
    }

    public void setApprovalStore(ApprovalStore approvalStore) {
        this.approvalStore = approvalStore;
    }

    public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {
        return this.readAuthentication(token.getValue());
    }


    public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
    }

    public OAuth2AccessToken readAccessToken(String tokenValue) {
        DefaultOAuth2AccessToken accessToken = (DefaultOAuth2AccessToken) this.convertAccessToken(tokenValue);
        OAuth2AccessToken newAccessToken = updateTokenOutTime(accessToken);
        if (this.jwtTokenEnhancer.isRefreshToken(accessToken)) {
            throw new InvalidTokenException("Encoded token is a refresh token");
        } else {
            return newAccessToken;
        }
    }

    private OAuth2AccessToken updateTokenOutTime(DefaultOAuth2AccessToken accessToken) {
        if(accessToken.getAdditionalInformation()!=null&&accessToken.getAdditionalInformation().containsKey("user_id")){
            //通过用户id获取redis存储的token过期时间
            Object userIdObj = accessToken.getAdditionalInformation().get("user_id");
            Integer value =(Integer) redisUtils.get("user:refreshToken:outTime_" + userIdObj);
            if (value!=null){
                //更新token过期时间为明天
                accessToken.setExpiration(DateUtil.addDay(new Date(),1));
                //将这个时间重新存到redis
                redisUtils.set("user:refreshToken:outTime_" + userIdObj, value, value);
            }
        }
        return accessToken;
    }

    private OAuth2AccessToken convertAccessToken(String tokenValue) {
        return this.jwtTokenEnhancer.extractAccessToken(tokenValue, decode(tokenValue));
    }

    public void removeAccessToken(OAuth2AccessToken token) {
    }

    public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {
    }

    public OAuth2RefreshToken readRefreshToken(String tokenValue) {
        OAuth2AccessToken encodedRefreshToken = this.convertAccessToken(tokenValue);
        OAuth2RefreshToken refreshToken = this.createRefreshToken(encodedRefreshToken);
        if (this.approvalStore != null) {
            OAuth2Authentication authentication = this.readAuthentication(tokenValue);
            if (authentication.getUserAuthentication() != null) {
                String userId = authentication.getUserAuthentication().getName();
                String clientId = authentication.getOAuth2Request().getClientId();
                Collection<Approval> approvals = this.approvalStore.getApprovals(userId, clientId);
                Collection<String> approvedScopes = new HashSet();
                Iterator var9 = approvals.iterator();

                while(var9.hasNext()) {
                    Approval approval = (Approval)var9.next();
                    if (approval.isApproved()) {
                        approvedScopes.add(approval.getScope());
                    }
                }

                if (!approvedScopes.containsAll(authentication.getOAuth2Request().getScope())) {
                    return null;
                }
            }
        }

        return refreshToken;
    }

    private OAuth2RefreshToken createRefreshToken(OAuth2AccessToken encodedRefreshToken) {
        if (!this.jwtTokenEnhancer.isRefreshToken(encodedRefreshToken)) {
            throw new InvalidTokenException("Encoded token is not a refresh token");
        } else {
            return (OAuth2RefreshToken)(encodedRefreshToken.getExpiration() != null ? new DefaultExpiringOAuth2RefreshToken(encodedRefreshToken.getValue(), encodedRefreshToken.getExpiration()) : new DefaultOAuth2RefreshToken(encodedRefreshToken.getValue()));
        }
    }

    public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) {
        return this.readAuthentication(token.getValue());
    }

    public void removeRefreshToken(OAuth2RefreshToken token) {
        this.remove(token.getValue());
    }

    public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) {
    }

    public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
        return null;
    }

    public Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName) {
        return Collections.emptySet();
    }

    public Collection<OAuth2AccessToken> findTokensByClientId(String clientId) {
        return Collections.emptySet();
    }

    public void setTokenEnhancer(JwtAccessTokenConverter tokenEnhancer) {
        this.jwtTokenEnhancer = tokenEnhancer;
    }

    private void remove(String token) {
        if (this.approvalStore != null) {
            OAuth2Authentication auth = this.readAuthentication(token);
            String clientId = auth.getOAuth2Request().getClientId();
            Authentication user = auth.getUserAuthentication();
            if (user != null) {
                Collection<Approval> approvals = new ArrayList();
                Iterator var6 = auth.getOAuth2Request().getScope().iterator();

                while(var6.hasNext()) {
                    String scope = (String)var6.next();
                    approvals.add(new Approval(user.getName(), clientId, scope, new Date(), ApprovalStatus.APPROVED));
                }

                this.approvalStore.revokeApprovals(approvals);
            }
        }

    }

    protected Map<String, Object> decode(String token) {
        try {
            Jwt jwt = JwtHelper.decodeAndVerify(token,verifier);
            String claimsStr = jwt.getClaims();
            Map<String, Object> claims = this.objectMapper.parseMap(claimsStr);
            if (claims.containsKey("exp") && claims.get("exp") instanceof Integer) {
                Integer intValue = (Integer)claims.get("exp");
                claims.put("exp", new Long((long)intValue));
            }

            jwtClaimsSetVerifier.verify(claims);
            return claims;
        } catch (Exception var6) {
            throw new InvalidTokenException("Cannot convert access token to JSON", var6);
        }
    }


    private class NoOpJwtClaimsSetVerifier implements JwtClaimsSetVerifier {
        private NoOpJwtClaimsSetVerifier() {
        }

        public void verify(Map<String, Object> claims) throws InvalidTokenException {
        }
    }
}
