package com.dne.ems.service.impl;

import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

import com.dne.ems.service.JwtService;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;

/**
 * JWT服务实现类，处理JWT令牌的生成、解析和验证
 * 
 * <p>该服务负责实现JWT（JSON Web Token）相关的所有功能，包括：
 * <ul>
 *   <li>生成包含用户信息的JWT令牌</li>
 *   <li>验证令牌的有效性和完整性</li>
 *   <li>从令牌中提取用户信息和声明</li>
 *   <li>处理令牌的签名和过期时间</li>
 * </ul>
 *
 * @author DNE开发团队
 * @version 1.0
 * @since 2024
 */
@Service
public class JwtServiceImpl implements JwtService {
    @Value("${token.signing.key}")
    private String jwtSigningKey;

    /**
     * {@inheritDoc}
     *
     * <p>实现细节：
     * <ol>
     *   <li>从令牌中提取所有声明</li>
     *   <li>获取主题声明（Subject）作为用户名</li>
     * </ol>
     */
    @Override
    public String extractUserName(String token) {
        return extractClaim(token, Claims::getSubject);
    }

    /**
     * {@inheritDoc}
     *
     * <p>实现细节：
     * <ol>
     *   <li>检查是否为自定义用户详情类型</li>
     *   <li>提取用户信息作为额外声明</li>
     *   <li>添加用户权限到声明中</li>
     *   <li>生成带有签名的JWT令牌</li>
     * </ol>
     */
    @Override
    public String generateToken(UserDetails userDetails) {
        // 使用 instanceof 模式匹配 (Java 16+) 来简化代码
        if (userDetails instanceof com.dne.ems.security.CustomUserDetails customUserDetails) {
            Map<String, Object> extraClaims = new HashMap<>();

            // 将所有需要暴露给前端的用户信息放入 "claims"
            // 注意：我们不应放入密码等敏感信息
            extraClaims.put("id", customUserDetails.getId());
            extraClaims.put("name", customUserDetails.getName());
            extraClaims.put("email", customUserDetails.getUsername()); // getUsername() 返回的是 email
            extraClaims.put("role", customUserDetails.getRole());
            extraClaims.put("phone", customUserDetails.getPhone());
            extraClaims.put("status", customUserDetails.getStatus());
            extraClaims.put("region", customUserDetails.getRegion());
            extraClaims.put("skills", customUserDetails.getSkills());
            
            // 关键修复：将用户的权限（GrantedAuthority）转换为字符串列表并添加到 claims 中
            // Spring Security 的 hasAuthority() 会检查这个 "authorities" 列表
            extraClaims.put("authorities", customUserDetails.getAuthorities().stream()
                    .map(auth -> auth.getAuthority())
                    .collect(java.util.stream.Collectors.toList()));

            // 使用重载的方法生成带有额外信息的 Token
            return generateToken(extraClaims, userDetails);
        }

        // 如果不是我们的 CustomUserDetails 类型，则生成一个只包含基本信息的 Token
        return generateToken(new HashMap<>(), userDetails);
    }

    /**
     * {@inheritDoc}
     *
     * <p>实现细节：
     * <ol>
     *   <li>从令牌中提取用户名</li>
     *   <li>验证用户名是否匹配</li>
     *   <li>检查令牌是否已过期</li>
     * </ol>
     */
    @Override
    public boolean isTokenValid(String token, UserDetails userDetails) {
        final String userName = extractUserName(token);
        return (userName.equals(userDetails.getUsername())) && !isTokenExpired(token);
    }

    /**
     * 从令牌中提取特定声明
     *
     * @param token 令牌字符串
     * @param claimsResolvers 声明解析函数
     * @return 解析后的声明值
     */
    private <T> T extractClaim(String token, Function<Claims, T> claimsResolvers) {
        final Claims claims = extractAllClaims(token);
        return claimsResolvers.apply(claims);
    }

    /**
     * 生成JWT令牌
     *
     * @param extraClaims 额外的声明信息
     * @param userDetails 用户详情
     * @return 生成的JWT令牌字符串
     */
    private String generateToken(Map<String, Object> extraClaims, UserDetails userDetails) {
        return Jwts.builder().setClaims(extraClaims).setSubject(userDetails.getUsername())
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 24)) // 1 day
                .signWith(getSigningKey(), SignatureAlgorithm.HS256).compact();
    }

    /**
     * 检查令牌是否已过期
     *
     * @param token 令牌字符串
     * @return 如果令牌已过期则返回true，否则返回false
     */
    private boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }

    /**
     * 从令牌中提取过期时间
     *
     * @param token 令牌字符串
     * @return 令牌的过期时间
     */
    private Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }

    /**
     * {@inheritDoc}
     *
     * <p>实现细节：
     * <ol>
     *   <li>使用签名密钥解析令牌</li>
     *   <li>提取令牌主体中的所有声明</li>
     * </ol>
     */
    @Override
    public Claims extractAllClaims(String token) {
        return Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(token)
                .getBody();
    }

    /**
     * 获取用于签名的密钥
     *
     * @return 用于JWT签名的密钥对象
     */
    private Key getSigningKey() {
        byte[] keyBytes = Decoders.BASE64.decode(jwtSigningKey);
        return Keys.hmacShaKeyFor(keyBytes);
    }
}