package cn.teammodel.security.utils;

import cn.hutool.extra.spring.SpringUtil;
import cn.teammodel.model.entity.TmdUserDetail;
import cn.teammodel.model.entity.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.userdetails.UserDetails;

import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author winter
 * @date 2022年11月24日 下午10:50
 * @description 生成jwt令牌的工具类
 */
@Slf4j
public class JwtTokenUtil {
    private static final long NEVER_EXPIRE = 315360000 ; // 没有永不过期的api: 让时钟偏移十年

    private final String secret;

    {
        secret = SpringUtil.getProperty("jwt.secret");
    }

    /**
     * 生成token
     * @param userDetails 传入userDetails
     * @return token
     */
    @Deprecated
    public String generateToken(UserDetails userDetails){
        Map<String, Object> claims = new HashMap<>();
        // 添加 payload
        //claims.put(CLAIM_KEY_USERNAME,userDetails.getUsername());
        //claims.put(CLAIM_KEY_CREATED,new Date());
        return generateToken(claims);
    }
    @Deprecated
    private String generateToken(Map<String,Object> claims){
        return Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512,secret)
                .compact();
    }

    /**
     * 从token中获取用户名
     * @param token 前端传入的token
     * @return 负载中的用户名,获取失败返回null
     */
    @Deprecated
    public String getUsernameFromToken(String token) {
        String username = null;
        try {
            Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        }catch (Exception e){
            log.info("jwt验证出错:{}",token);
        }
        return username;
    }

    /**
     * 从token中获取Claims，异常返回null
     * @param token token
     * @return claims或者null
     */
    private Claims getClaimsFromToken(String token) {
        return validAndGetClaims(token, secret, NEVER_EXPIRE);
    }

    public Claims validAndGetClaims(String token, String key, long expire) {
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256"))
                    .setAllowedClockSkewSeconds(expire)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            log.warn("token 解析出错:{}",e.getMessage());
        }
        return claims;
    }

    /**
     * 检验token是否还有效
     * @param token 前端传入的token
     * @return true表示有效，false表示无效
     */
    public boolean validateToken(String token) {
        return !isTokenExpired(token);
    }

    public boolean validateToken(HttpServletRequest request) {
        String token = request.getHeader("x-auth-AuthToken");
        if (StringUtils.isBlank(token)) {
            return false;
        }

        return validateToken(token);
    }

    private boolean isTokenExpired(String token) {
        Date expiredDate = getExpiredDateFromToken(token);
        return expiredDate.before(new Date());
    }

    private Date getExpiredDateFromToken(String token) {
        Claims claims = getClaimsFromToken(token);
        return claims.getExpiration();
    }

    /**
     * token 验证成功返回实体,验证失败返回 null
     * @param request:
     * @return: cn.teammodel.model.entity.TmdUserDetail
     * @author: winter
     * @date: 2023/11/10 10:24
     * @description:
     */
    public TmdUserDetail getValidUserDetail(HttpServletRequest request) {
        String token = request.getHeader("x-auth-AuthToken");
        if (StringUtils.isBlank(token)) {
            return null;
        }
        Claims claims = getClaimsFromToken(token);
        if (claims == null) {
            return null;
        }

        // 组装 TmdUserDetail
        TmdUserDetail tmdUserDetail = new TmdUserDetail();
        User user = new User();
        String id = claims.getSubject();
        user.setId(id);
        user.setName(claims.get("name") == null ? null : claims.get("name", String.class));
        user.setSchoolId(claims.get("azp") == null ? null : claims.get("azp", String.class));
        user.setPicture(claims.get("picture") == null ? null : claims.get("picture", String.class));
        user.setStandard(claims.get("standard") == null ? null : claims.get("standard", String.class));
        user.setScope(claims.get("scope") == null ? null : claims.get("scope", String.class));
        user.setWebsite(claims.get("website") == null ? null : claims.get("website", String.class));
        user.setArea(claims.get("area") == null ? null : claims.get("area", String.class));

        // 取出 roles 和 permissions
        Set<String> roleSet = convertToArray(claims.get("roles"));
        Set<String> permissionSet = convertToArray(claims.get("permissions"));
        user.setRoles(roleSet);
        user.setPermissions(permissionSet);
        tmdUserDetail.setUser(user);
        return tmdUserDetail;
    }

    public TmdUserDetail getOutterTokenDetail(HttpServletRequest request) {
        String token = request.getHeader("X-Auth-Authtoken");
        if (StringUtils.isBlank(token)) {
            return null;
        }
        Claims claims = getClaimsFromToken(token);
        if (claims == null) {
            return null;
        }

        // 组装 TmdUserDetail
        TmdUserDetail tmdUserDetail = new TmdUserDetail();
        tmdUserDetail.setClaims(claims);
        return tmdUserDetail;
    }

    @SuppressWarnings("unchecked")
    private Set<String> convertToArray(Object o) {
        if (o == null) {
            return Collections.emptySet();
        }
        if (o instanceof String) {
            if (StringUtils.isNotBlank((String)o)) {
                return Arrays.stream(((String) o).split(" ")).collect(Collectors.toSet());
            }
            return Collections.emptySet();
        }
        if (o instanceof Collection) {
            return new HashSet<>(((Collection<String>) o));
        }
        return Collections.emptySet();
    }
}