package com.chinaweal.aiccs.common.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.Random;

/**
 * JWT令牌工具类
 *
 * @author chinaweal
 * @since 2025-11-21
 */
@Slf4j
@Component
public class JwtTokenUtil {

    /**
     * 刷新令牌长度
     */
    private static final int REFRESH_TOKEN_LENGTH = 64;

    /**
     * 刷新令牌字符集
     */
    private static final String REFRESH_TOKEN_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

    @Value("${oauth.jwt.secret:youfool-oauth-jwt-secret-key-2025}")
    private String jwtSecret;

    @Value("${oauth.jwt.issuer:youfool-pms}")
    private String jwtIssuer;

    private final Random random = new Random();

    /**
     * 生成访问令牌
     *
     * @param userId 用户ID
     * @param userName 用户名
     * @param clientId 客户端ID
     * @param scope 授权范围
     * @param expiresIn 过期时间（分钟）
     * @return JWT访问令牌
     */
    public String generateAccessToken(String userId, String userName, String clientId, String scope, int expiresIn) {
        try {
            LocalDateTime now = LocalDateTime.now();
            LocalDateTime expirationTime = now.plusMinutes(expiresIn);

            return Jwts.builder()
                    .setSubject(userId)
                    .claim("username", userName)
                    .claim("client_id", clientId)
                    .claim("scope", scope)
                    .claim("token_type", "access_token")
                    .setIssuer(jwtIssuer)
                    .setIssuedAt(Date.from(now.atZone(ZoneId.systemDefault()).toInstant()))
                    .setExpiration(Date.from(expirationTime.atZone(ZoneId.systemDefault()).toInstant()))
                    .signWith(SignatureAlgorithm.HS256, jwtSecret.getBytes(StandardCharsets.UTF_8))
                    .compact();

        } catch (Exception e) {
            log.error("生成访问令牌失败，用户ID: {}, 客户端ID: {}", userId, clientId, e);
            throw new RuntimeException("生成访问令牌失败: " + e.getMessage());
        }
    }

    /**
     * 生成刷新令牌
     *
     * @return 刷新令牌
     */
    public String generateRefreshToken() {
        StringBuilder sb = new StringBuilder(REFRESH_TOKEN_LENGTH);
        for (int i = 0; i < REFRESH_TOKEN_LENGTH; i++) {
            sb.append(REFRESH_TOKEN_CHARS.charAt(random.nextInt(REFRESH_TOKEN_CHARS.length())));
        }
        return sb.toString();
    }

    /**
     * 解析JWT令牌
     *
     * @param token JWT令牌
     * @return 声明信息
     */
    public Claims parseToken(String token) {
        try {
            if (StringUtils.isBlank(token)) {
                return null;
            }

            return Jwts.parser()
                    .setSigningKey(jwtSecret.getBytes(StandardCharsets.UTF_8))
                    .parseClaimsJws(token)
                    .getBody();

        } catch (Exception e) {
            log.warn("解析JWT令牌失败，令牌: {}", token, e);
            return null;
        }
    }

    /**
     * 验证JWT令牌是否有效
     *
     * @param token JWT令牌
     * @return 是否有效
     */
    public boolean validateToken(String token) {
        try {
            if (StringUtils.isBlank(token)) {
                return false;
            }

            Claims claims = parseToken(token);
            if (claims == null) {
                return false;
            }

            // 检查过期时间
            Date expiration = claims.getExpiration();
            if (expiration != null && expiration.before(new Date())) {
                log.warn("JWT令牌已过期，过期时间: {}", expiration);
                return false;
            }

            // 检查发行者
            String issuer = claims.getIssuer();
            if (!jwtIssuer.equals(issuer)) {
                log.warn("JWT令牌发行者不匹配，期望: {}, 实际: {}", jwtIssuer, issuer);
                return false;
            }

            return true;

        } catch (Exception e) {
            log.warn("验证JWT令牌失败，令牌: {}", token, e);
            return false;
        }
    }

    /**
     * 从令牌中获取用户ID
     *
     * @param token JWT令牌
     * @return 用户ID
     */
    public String getUserIdFromToken(String token) {
        try {
            Claims claims = parseToken(token);
            return claims != null ? claims.getSubject() : null;
        } catch (Exception e) {
            log.error("从令牌中获取用户ID失败，令牌: {}", token, e);
            return null;
        }
    }

    /**
     * 从令牌中获取用户名
     *
     * @param token JWT令牌
     * @return 用户名
     */
    public String getUserNameFromToken(String token) {
        try {
            Claims claims = parseToken(token);
            return claims != null ? claims.get("username", String.class) : null;
        } catch (Exception e) {
            log.error("从令牌中获取用户名失败，令牌: {}", token, e);
            return null;
        }
    }

    /**
     * 从令牌中获取客户端ID
     *
     * @param token JWT令牌
     * @return 客户端ID
     */
    public String getClientIdFromToken(String token) {
        try {
            Claims claims = parseToken(token);
            return claims != null ? claims.get("client_id", String.class) : null;
        } catch (Exception e) {
            log.error("从令牌中获取客户端ID失败，令牌: {}", token, e);
            return null;
        }
    }

    /**
     * 从令牌中获取授权范围
     *
     * @param token JWT令牌
     * @return 授权范围
     */
    public String getScopeFromToken(String token) {
        try {
            Claims claims = parseToken(token);
            return claims != null ? claims.get("scope", String.class) : null;
        } catch (Exception e) {
            log.error("从令牌中获取授权范围失败，令牌: {}", token, e);
            return null;
        }
    }

    /**
     * 获取令牌剩余有效时间（秒）
     *
     * @param token JWT令牌
     * @return 剩余有效时间（秒），-1表示无效
     */
    public long getRemainingValiditySeconds(String token) {
        try {
            Claims claims = parseToken(token);
            if (claims == null) {
                return -1;
            }

            Date expiration = claims.getExpiration();
            if (expiration == null) {
                return -1;
            }

            long remaining = (expiration.getTime() - System.currentTimeMillis()) / 1000;
            return remaining > 0 ? remaining : -1;

        } catch (Exception e) {
            log.error("获取令牌剩余有效时间失败，令牌: {}", token, e);
            return -1;
        }
    }

    /**
     * 检查令牌是否即将过期（在指定分钟内过期）
     *
     * @param token JWT令牌
     * @param minutesWithin 指定的分钟数
     * @return 是否即将过期
     */
    public boolean isTokenExpiringSoon(String token, int minutesWithin) {
        try {
            Claims claims = parseToken(token);
            if (claims == null) {
                return true;
            }

            Date expiration = claims.getExpiration();
            if (expiration == null) {
                return true;
            }

            long remainingMinutes = (expiration.getTime() - System.currentTimeMillis()) / (60 * 1000);
            return remainingMinutes <= minutesWithin;

        } catch (Exception e) {
            log.error("检查令牌是否即将过期失败，令牌: {}", token, e);
            return true;
        }
    }
}