package com.jeeplus.gateway.security;

import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.jeeplus.common.constant.CacheNames;
import com.jeeplus.common.constant.ErrorConstants;
import com.jeeplus.common.redis.RedisUtils;
import com.jeeplus.config.properties.JeePlusProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpCookie;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Slf4j
@Component
public class TokenProvider {
    public static final String TOKEN = "token";

    public static final String SIGN_CODE = "@#&#$^JWE@#$F123";

    /**
     * 校验token是否正确
     *
     * @param token 密钥
     * @return 是否正确
     */
    public boolean validateToken(String token, String accessToken) {
        String userName = TokenProvider.getLoginName ( accessToken );
        try {

            Algorithm algorithm = Algorithm.HMAC256 ( SIGN_CODE );
            JWTVerifier verifier = JWT.require ( algorithm )
                    .withClaim ( "username", userName )
                    .build ( );
            verifier.verify ( accessToken );
            return true;
        } catch (TokenExpiredException e) {
            // token失效，执行刷新操作
            String newAccessToken = createAccessToken ( userName );
            RedisUtils.getInstance ( ).set ( CacheNames.USER_CACHE_TOKEN, token, newAccessToken );
            RedisUtils.getInstance ( ).expire ( CacheNames.USER_CACHE_TOKEN, token, JeePlusProperties.newInstance ( ).getEXPIRE_TIME ( ) );
            RedisUtils.getInstance ( ).expire ( CacheNames.USER_CACHE_ONLINE_USERS, token, JeePlusProperties.newInstance ( ).getEXPIRE_TIME ( ) );
            return true;
        } catch (Exception e) {
            log.error ( ErrorConstants.LOGIN_ERROR_INCORRECT );
        }
        return false;

    }

    /**
     * 获得token中的信息无需secret解密也能获得
     *
     * @return token中包含的用户名
     */
    public static String getLoginName(String token) {
        try {
            DecodedJWT jwt = JWT.decode ( token );
            return jwt.getClaim ( "username" ).asString ( );
        } catch (Exception e) {
            return null;
        }
    }


    public static Authentication getAuthentication(String token) {
        DecodedJWT jwt = JWT.decode ( token );
        String username = jwt.getClaim ( "username" ).asString ( );

        // 权限集合
        List <GrantedAuthority> authorities = new ArrayList <> ( );
        org.springframework.security.core.userdetails.User principal = new org.springframework.security.core.userdetails.User ( username, "", authorities );

        return new UsernamePasswordAuthenticationToken ( principal, token, authorities );
    }

    /**
     * 生成签名
     *
     * @param username 用户名
     * @return 加密的token
     */
    public static String createAccessToken(String username) {
        Date date = new Date ( System.currentTimeMillis ( ) + JeePlusProperties.newInstance ( ).getEXPIRE_TIME ( ) * 1000 );
        Algorithm algorithm = Algorithm.HMAC256 ( SIGN_CODE );
        // 附带username信息
        return JWT.create ( )
                .withClaim ( "username", username )
                .withExpiresAt ( date )
                .sign ( algorithm );
    }

    /**
     * 获取token，支持三种方式, 请求参数、header、cookie， 优先级依次降低，以请求参数中的优先级最高。
     *
     * @param request
     * @return
     */
    public static String resolveToken(ServerHttpRequest request) {
        String token0 = request.getQueryParams ( ).getFirst ( TokenProvider.TOKEN );
        String token1 = request.getHeaders ( ).getFirst ( TokenProvider.TOKEN );
        HttpCookie token2 = request.getCookies ( ).getFirst ( TokenProvider.TOKEN );
        if ( StrUtil.isNotBlank ( token0 ) ) {
            return token0;
        }
        if ( StrUtil.isNotBlank ( token1 ) ) {
            return token1;
        }
        if ( token2 != null && StrUtil.isNotBlank ( token2.getValue ( ) ) ) {
            return token2.getValue ( );
        }
        return null;
    }
}
