package com.xindazhou.framework.security.core.jwt;

import cn.hutool.core.io.resource.ResourceUtil;
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.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Base64;
import java.util.Date;
import java.util.List;

/**
 * OAuth2 JWT 工具类
 * 用于生成和验证 OAuth2 Access Token（JWT 格式）
 *
 * @author xindazhou
 */
@Slf4j
public class OAuth2JwtUtil {

    private static RSAPrivateKey privateKey;
    private static RSAPublicKey publicKey;
    private static Algorithm algorithm;
    private static JWTVerifier verifier;
    private static String issuer = "xindazhou-oauth2";
    private static Long defaultExpireTime = 7200L; // 默认 2 小时

    /**
     * 初始化密钥对
     *
     * @param privateKeyPath 私钥路径（用于生成 Token，仅用户服务需要）
     * @param publicKeyPath  公钥路径（用于验证 Token，所有服务都需要）
     * @param expireTime     默认过期时间（秒）
     * @param resourceLoader Spring ResourceLoader
     */
    public static void init(String privateKeyPath, String publicKeyPath, Long expireTime, ResourceLoader resourceLoader) {
        try {
            if (expireTime != null && expireTime > 0) {
                OAuth2JwtUtil.defaultExpireTime = expireTime;
            }

            // 加载私钥（用于生成 Token）
            if (StrUtil.isNotEmpty(privateKeyPath)) {
                String privateKeyContent = readKeyFile(privateKeyPath, resourceLoader);
                privateKey = getPrivateKey(privateKeyContent);
                log.info("OAuth2 JWT 私钥初始化成功: {}", privateKeyPath);
            }

            // 加载公钥（用于验证 Token）
            if (StrUtil.isNotEmpty(publicKeyPath)) {
                String publicKeyContent = readKeyFile(publicKeyPath, resourceLoader);
                publicKey = getPublicKey(publicKeyContent);
                // 创建验证算法：如果只有公钥（验证场景），使用 Algorithm.RSA256(publicKey, null)
                // 如果有私钥（生成场景），使用 Algorithm.RSA256(publicKey, privateKey)
                algorithm = Algorithm.RSA256(publicKey, privateKey);
                verifier = JWT.require(algorithm).build();
                log.info("OAuth2 JWT 公钥初始化成功: {}, privateKeyPresent={}", publicKeyPath, privateKey != null);
            }

            log.info("OAuth2 JWT 初始化完成: expireTime={}s", OAuth2JwtUtil.defaultExpireTime);
        } catch (Exception e) {
            log.error("OAuth2 JWT 初始化失败: privateKeyPath={}, publicKeyPath={}", privateKeyPath, publicKeyPath, e);
            throw new RuntimeException("OAuth2 JWT 初始化失败", e);
        }
    }

    /**
     * 生成 OAuth2 JWT Token
     *
     * @param userId     用户ID
     * @param userType   用户类型（1=C端，2=管理后台）
     * @param tenantId   租户ID
     * @param scopes     权限范围
     * @param expireTime 过期时间（秒）
     * @return JWT Token
     */
    public static String generateToken(Long userId, Integer userType, Long tenantId, List<String> scopes, Long expireTime) {
        if (privateKey == null) {
            log.error("OAuth2 JWT 私钥未初始化，无法生成 Token");
            return null;
        }
        try {
            Date now = new Date();
            Date expiresAt = new Date(now.getTime() + (expireTime != null ? expireTime : defaultExpireTime) * 1000);

            return JWT.create()
                    .withIssuer(issuer) // 签发者
                    .withSubject(userId != null ? String.valueOf(userId) : "system") // 主题（用户ID）
                    .withClaim("uid", userId) // 用户ID
                    .withClaim("userType", userType) // 用户类型
                    .withClaim("tenantId", tenantId) // 租户ID
                    .withClaim("scopes", scopes != null ? String.join(",", scopes) : null) // 权限范围
                    .withIssuedAt(now) // 签发时间
                    .withExpiresAt(expiresAt) // 过期时间
                    .sign(Algorithm.RSA256(privateKey)); // 使用私钥签名
        } catch (Exception e) {
            log.error("生成 OAuth2 JWT Token 失败: userId={}, userType={}, tenantId={}", userId, userType, tenantId, e);
            return null;
        }
    }

    /**
     * 验证 OAuth2 JWT Token
     *
     * @param token JWT Token
     * @return 解码后的 JWT，验证失败返回 null
     */
    public static DecodedJWT verifyToken(String token) {
        if (StrUtil.isEmpty(token)) {
            log.warn("OAuth2 JWT Token 验证失败: Token 为空");
            return null;
        }
        if (verifier == null) {
            log.warn("OAuth2 JWT 验证器未初始化");
            return null;
        }
        try {
            return verifier.verify(token);
        } catch (JWTVerificationException e) {
            // 将日志级别从 debug 改为 warn，以便排查问题
            log.warn("OAuth2 JWT Token 验证失败: {}, tokenPrefix={}", 
                    e.getMessage(), 
                    token.length() > 20 ? token.substring(0, 20) + "..." : token);
            return null;
        } catch (Exception e) {
            log.error("OAuth2 JWT Token 验证异常: {}", e.getMessage(), e);
            return null;
        }
    }

    /**
     * 从 JWT 中获取用户ID
     */
    public static Long getUserId(DecodedJWT jwt) {
        if (jwt == null) {
            return null;
        }
        try {
            return jwt.getClaim("uid").asLong();
        } catch (Exception e) {
            log.warn("从 JWT 中获取用户ID 失败: {}", e.getMessage());
            return null;
        }
    }

    /**
     * 从 JWT 中获取用户类型
     */
    public static Integer getUserType(DecodedJWT jwt) {
        if (jwt == null) {
            return null;
        }
        try {
            return jwt.getClaim("userType").asInt();
        } catch (Exception e) {
            log.warn("从 JWT 中获取用户类型失败: {}", e.getMessage());
            return null;
        }
    }

    /**
     * 从 JWT 中获取租户ID
     */
    public static Long getTenantId(DecodedJWT jwt) {
        if (jwt == null) {
            return null;
        }
        try {
            return jwt.getClaim("tenantId").asLong();
        } catch (Exception e) {
            log.warn("从 JWT 中获取租户ID失败: {}", e.getMessage());
            return null;
        }
    }

    /**
     * 从 JWT 中获取权限范围
     */
    public static List<String> getScopes(DecodedJWT jwt) {
        if (jwt == null) {
            return null;
        }
        try {
            String scopesStr = jwt.getClaim("scopes").asString();
            if (StrUtil.isEmpty(scopesStr)) {
                return null;
            }
            return List.of(scopesStr.split(","));
        } catch (Exception e) {
            log.warn("从 JWT 中获取权限范围失败: {}", e.getMessage());
            return null;
        }
    }

    /**
     * 从 JWT 中获取过期时间
     */
    public static LocalDateTime getExpiresTime(DecodedJWT jwt) {
        if (jwt == null || jwt.getExpiresAt() == null) {
            return null;
        }
        return LocalDateTime.ofInstant(jwt.getExpiresAt().toInstant(), ZoneId.systemDefault());
    }

    /**
     * 从 JWT 中获取签发时间
     */
    public static LocalDateTime getIssuedAt(DecodedJWT jwt) {
        if (jwt == null || jwt.getIssuedAt() == null) {
            return null;
        }
        return LocalDateTime.ofInstant(jwt.getIssuedAt().toInstant(), ZoneId.systemDefault());
    }

    /**
     * 读取密钥文件内容
     */
    private static String readKeyFile(String keyPath, ResourceLoader resourceLoader) throws IOException {
        if (keyPath.startsWith("classpath:")) {
            Resource resource = resourceLoader.getResource(keyPath);
            return new String(resource.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
        } else {
            return ResourceUtil.readUtf8Str(keyPath);
        }
    }

    /**
     * 从 PEM 格式的公钥字符串中提取 RSA 公钥
     */
    private static RSAPublicKey getPublicKey(String publicKeyContent) throws Exception {
        String publicKeyPEM = publicKeyContent
                .replace("-----BEGIN PUBLIC KEY-----", "")
                .replace("-----END PUBLIC KEY-----", "")
                .replaceAll("\\s", "");
        byte[] keyBytes = Base64.getDecoder().decode(publicKeyPEM);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return (RSAPublicKey) keyFactory.generatePublic(spec);
    }

    /**
     * 从 PEM 格式的私钥字符串中提取 RSA 私钥
     */
    private static RSAPrivateKey getPrivateKey(String privateKeyContent) throws Exception {
        String privateKeyPEM = privateKeyContent
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s", "");
        byte[] keyBytes = Base64.getDecoder().decode(privateKeyPEM);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return (RSAPrivateKey) keyFactory.generatePrivate(spec);
    }

}

