package com.wx.application.util;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.SecretKey;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;

import com.alibaba.fastjson.JSON;
import com.wx.application.base.ErrorCodeEnum;
import com.wx.application.constant.CONSTANT;
import com.wx.application.core.entity.RiskUser;
import com.wx.application.exception.CommonException;

import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.DigestAlgorithm;
import cn.hutool.crypto.digest.Digester;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import lombok.Data;
import lombok.experimental.Accessors;

public class JwtUtils {

    @Data
    @Accessors(chain = true)
    public static class LoginUser {
        private String userType;
        private Long userId;
        private String openId;
        private String appId;
    }


    //加密因子
    private static final SecretKey key = SecureUtil.generateKey("HmacSHA256", "AbCDeeeeeeass2SASDFFFFFF!##@$%^%@FF".getBytes());
    /**
     * 默认过期时间 :12小时 并不想失效
     */
    //private static final Long expiration = 12 * 60 * 60 * 1000L;
    /**
     * JWT 添加至HTTP HEAD中的前缀
     */
    private static final String JWT_SEPARATOR = "Bearer ";


    public static void initClaims() {
        String token = MDC.get(JWT_SEPARATOR);
        if (StringUtils.isBlank(token)) {
            throw new CommonException(ErrorCodeEnum.UNAUTHORIZED);
        }

        Claims claims = Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();
        String subject = claims.getSubject();
        String userType = (String) claims.get(CONSTANT.JWT_USER_TAG);
        MDC.put(userType, subject);
    }

    public static RiskUser getCurrentLoginAdminUser() {
        initClaims();
        String subject = MDC.get(CONSTANT.LOGIN_TYPE_ADMIN);
        if (StringUtils.isBlank(subject)) {
            throw new CommonException(ErrorCodeEnum.UNAUTHORIZED);
        }
        RiskUser user = JSON.parseObject(subject, RiskUser.class);
        if (null == user) {
            throw new CommonException(ErrorCodeEnum.UNAUTHORIZED);
        }
        return user;
    }
    
    /**
     * 新建一个jwt对象
     *
     * @param id      对象id
     * @param subject 对象内容
     * @return
     */
    public static String generateJwtToken(Long id, String userType, Object subject) {
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        Map<String, Object> claims = new HashMap<String, Object>();//创建payload的私有声明（根据特定的业务需要添加，如果要拿这个做验证，一般是需要和jwt的接收方提前沟通好验证方式的）
        claims.put(CONSTANT.JWT_USER_TAG, userType);
        //下面就是在为payload添加各种标准声明和私有声明了
        String compact = Jwts.builder()
                .setClaims(claims)///如果有私有声明，一定要先设置这个自己创建的私有的声明，这个是给builder的claim赋值，一旦写在标准的声明赋值之后，就是覆盖了那些标准的声明的
                .setId(new Digester(DigestAlgorithm.MD5).digestHex(userType + id))//设置jti(JWT ID)：是JWT的唯一标识
                .setIssuedAt(now)//iat: jwt的签发时间
                .setNotBefore(now)
                .setSubject(JSON.toJSONString(subject))  //sub(Subject)：代表这个JWT的主体，即它的所有人
//                .setExpiration(new Date(nowMillis + expiration))
                .signWith(key).compact();

        // 在JWT字符串前添加"Bearer "字符串，用于加入"Authorization"请求头
        return JWT_SEPARATOR + compact;
    }

    /**
     * 检查token是否有效
     *
     * @param jsonWebToken
     * @return
     */
    public static boolean checkTokenExpired(String jsonWebToken) {
        try {
            if (StringUtils.startsWith(jsonWebToken, JWT_SEPARATOR)) {
                jsonWebToken = StringUtils.substringAfter(jsonWebToken, JWT_SEPARATOR);
            }
            Jwts.parser().setSigningKey(key).parseClaimsJws(jsonWebToken).getBody();

            //暂存下
            MDC.put(JWT_SEPARATOR, jsonWebToken);
            return true;
        } catch (ExpiredJwtException eje) {
            // Token过期
            eje.printStackTrace();
            return false;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

}
