package com.kt.event.common.security;

import com.kt.event.common.exception.ErrorCode;
import com.kt.event.common.exception.InfraException;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.List;

/**
 * JWT 토큰 생성 및 검증 제공자
 * Access Token 및 Refresh Token 생성/검증 기능 제공
 */
@Slf4j
@Component
public class JwtTokenProvider {

    /**
     * JWT 서명 키
     */
    private final SecretKey secretKey;

    /**
     * Access Token 유효기간 (밀리초)
     */
    private final long accessTokenValidityMs;

    /**
     * Refresh Token 유효기간 (밀리초)
     */
    private final long refreshTokenValidityMs;

    public JwtTokenProvider(
            @Value("${jwt.secret}") String secret,
            @Value("${jwt.access-token-validity:3600000}") long accessTokenValidityMs,
            @Value("${jwt.refresh-token-validity:604800000}") long refreshTokenValidityMs) {
        this.secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
        this.accessTokenValidityMs = accessTokenValidityMs;
        this.refreshTokenValidityMs = refreshTokenValidityMs;
    }

    /**
     * Access Token 생성
     *
     * @param userId 사용자 ID
     * @param storeId 매장 ID
     * @param email  이메일
     * @param name   이름
     * @param roles  역할 목록
     * @return Access Token
     */

    public String createAccessToken(String userId, String storeId, String email, String name, List<String> roles) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + accessTokenValidityMs);

        return Jwts.builder()
                .subject(userId)
                .claim("storeId", storeId)
                .claim("email", email)
                .claim("name", name)
                .claim("roles", roles)
                .claim("type", "access")
                .issuedAt(now)
                .expiration(expiryDate)
                .signWith(secretKey)
                .compact();
    }

    /**
     * Refresh Token 생성
     *
     * @param userId 사용자 ID
     * @return Refresh Token
     */
    public String createRefreshToken(String userId) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + refreshTokenValidityMs);

        return Jwts.builder()
                .subject(userId)
                .claim("type", "refresh")
                .issuedAt(now)
                .expiration(expiryDate)
                .signWith(secretKey)
                .compact();
    }

    /**
     * 토큰에서 사용자 ID 추출
     *
     * @param token JWT 토큰
     * @return 사용자 ID
     */
    public String getUserIdFromToken(String token) {
        Claims claims = parseToken(token);
        return claims.getSubject();
    }

    /**
     * 토큰에서 UserPrincipal 추출
     *
     * @param token JWT 토큰
     * @return UserPrincipal
     */
    public UserPrincipal getUserPrincipalFromToken(String token) {
        Claims claims = parseToken(token);

        String userId = claims.getSubject();
        String storeId = claims.get("storeId", String.class);
        String email = claims.get("email", String.class);
        String name = claims.get("name", String.class);
        @SuppressWarnings("unchecked")
        List<String> roles = claims.get("roles", List.class);

        return new UserPrincipal(userId, storeId, email, name, roles);
    }

    /**
     * 토큰 유효성 검증
     *
     * @param token JWT 토큰
     * @return 유효 여부
     */
    public boolean validateToken(String token) {
        try {
            parseToken(token);
            return true;
        } catch (SecurityException | MalformedJwtException e) {
            log.error("Invalid JWT signature: {}", e.getMessage());
        } catch (ExpiredJwtException e) {
            log.error("Expired JWT token: {}", e.getMessage());
        } catch (UnsupportedJwtException e) {
            log.error("Unsupported JWT token: {}", e.getMessage());
        } catch (IllegalArgumentException e) {
            log.error("JWT claims string is empty: {}", e.getMessage());
        }
        return false;
    }

    /**
     * 토큰 타입 확인 (access/refresh)
     *
     * @param token JWT 토큰
     * @return 토큰 타입
     */
    public String getTokenType(String token) {
        Claims claims = parseToken(token);
        return claims.get("type", String.class);
    }

    /**
     * Access Token 여부 확인
     *
     * @param token JWT 토큰
     * @return Access Token 여부
     */
    public boolean isAccessToken(String token) {
        return "access".equals(getTokenType(token));
    }

    /**
     * Refresh Token 여부 확인
     *
     * @param token JWT 토큰
     * @return Refresh Token 여부
     */
    public boolean isRefreshToken(String token) {
        return "refresh".equals(getTokenType(token));
    }

    /**
     * 토큰 파싱
     *
     * @param token JWT 토큰
     * @return Claims
     */
    private Claims parseToken(String token) {
        try {
            return Jwts.parser()
                    .verifyWith(secretKey)
                    .build()
                    .parseSignedClaims(token)
                    .getPayload();
        } catch (ExpiredJwtException e) {
            throw new InfraException(ErrorCode.AUTH_002, e);
        } catch (Exception e) {
            throw new InfraException(ErrorCode.AUTH_003, e);
        }
    }

    /**
     * 토큰 만료 시간 조회
     *
     * @param token JWT 토큰
     * @return 만료 시간
     */
    public Date getExpirationFromToken(String token) {
        Claims claims = parseToken(token);
        return claims.getExpiration();
    }

    /**
     * 토큰 발급 시간 조회
     *
     * @param token JWT 토큰
     * @return 발급 시간
     */
    public Date getIssuedAtFromToken(String token) {
        Claims claims = parseToken(token);
        return claims.getIssuedAt();
    }
}
