package com.spring.infra.security.jwt;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import com.spring.infra.security.domain.UserPrincipal;

import io.jsonwebtoken.Jwts;
import lombok.RequiredArgsConstructor;

/**
 * JWT 토큰을 생성하는 클래스입니다.
 * 
 * <p>이 클래스는 액세스 토큰과 리프레시 토큰을 생성하는 기능을 제공합니다.
 * 사용자의 인증 정보를 기반으로 JWT 토큰을 생성하며, 토큰의 유효 기간과 서명 알고리즘을 설정합니다.</p>
 * 
 * @author mindol
 * @version 1.0
 */
@Component
@RequiredArgsConstructor
public class JwtTokenGenerator {
    
    /**
     * JWT 토큰의 속성을 설정하는 클래스입니다.
     */
    private final JwtProperties jwtProperties;

    /**
     * JWT 서명 키를 제공하는 유틸리티 클래스입니다.
     */
    private final JwtTokenUtil jwtTokenUtil;

    /**
     * 액세스 토큰을 생성합니다.
     * 
     * <p>사용자의 인증 정보를 기반으로 JWT 액세스 토큰을 생성합니다.
     * 생성된 토큰은 지정된 만료 시간과 서명 알고리즘을 사용하여 서명됩니다.</p>
     * 
     * @param authentication 인증 정보
     * @return 생성된 액세스 토큰
     */
    public String generateAccessToken(Authentication authentication) {
        UserPrincipal user = (UserPrincipal) authentication.getPrincipal();
        return Jwts.builder()
                .setHeader(createHeader())
                .setClaims(createClaims(user))
                .setSubject(user.getUsername())
                .setIssuedAt(Date.from(Instant.now()))
                .setExpiration(Date.from(Instant.now().plus(jwtProperties.getAccessToken().getExpiration(), ChronoUnit.MINUTES))) 
                .signWith(jwtTokenUtil.getSigningKey(jwtProperties.getAccessToken().getSecret()))
                .compact();
    }

    /**
     * 리프레시 토큰을 생성합니다.
     * 
     * <p>사용자의 인증 정보를 기반으로 JWT 리프레시 토큰을 생성합니다.
     * 생성된 토큰은 지정된 만료 시간과 서명 알고리즘을 사용하여 서명됩니다.</p>
     * 
     * @param authentication 인증 정보
     * @return 생성된 리프레시 토큰
     */
    public String generateRefreshToken(Authentication authentication) {
        UserPrincipal user = (UserPrincipal) authentication.getPrincipal();
        return Jwts.builder()
                .setSubject(user.getKey())
                .setIssuedAt(Date.from(Instant.now()))
                .setExpiration(Date.from(Instant.now().plus(jwtProperties.getRefreshToken().getExpiration(), ChronoUnit.MINUTES))) 
                .signWith(jwtTokenUtil.getSigningKey(jwtProperties.getRefreshToken().getSecret()))
                .compact();
    }

    /**
     * JWT 헤더를 생성합니다.
     * 
     * <p>JWT 토큰의 헤더 정보를 설정하는 메서드입니다.
     * 헤더에는 토큰의 유형과 서명 알고리즘이 포함됩니다.</p>
     * 
     * @return JWT 헤더 맵
     */
    private Map<String, Object> createHeader() {
        Map<String, Object> header = new HashMap<>();
        header.put("typ", "JWT");
        header.put("alg", "HS512");
        return header;
    }

    /**
     * JWT 클레임을 생성합니다.
     * 
     * <p>사용자의 정보를 기반으로 JWT 클레임을 설정하는 메서드입니다.
     * 클레임에는 사용자 ID, 이름 및 권한 정보가 포함됩니다.</p>
     * 
     * @param user 사용자 정보
     * @return JWT 클레임 맵
     */
    private Map<String, Object> createClaims(UserPrincipal user) {
        Map<String, Object> claims = new HashMap<>();
        claims.put(JwtTokenRule.USER_ID.getValue(), user.getUsername());
        claims.put(JwtTokenRule.USER_NAME.getValue(), user.getMemberName());
        claims.put(JwtTokenRule.AUTHORITIES_KEY.getValue(), user.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList()));
        return claims;
    }

}
