package com.takensoft.common.util;

import com.takensoft.cms.mber.vo.MberAuthorVO;
import com.takensoft.cms.mber.vo.MberVO;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import jakarta.servlet.http.Cookie;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
 * @author  : takensoft
 * @since   : 2025.01.22
 * @modification
 *     since    |    author    | description
 *  2025.01.22  |  takensoft   | 최초 등록
 *
 * JWT 토큰 생성 및 검증, 쿠키 생성 등의 유틸리티
 */
@Component
public class JWTUtil {

    private static SecretKey JWT_SECRET_KEY;

    /**
     * @param secret - JWT 서명을 위한 키 (application.yml에서 값을 읽어 옴)
     *
     * 기본 생성자
     */
    public JWTUtil(@Value("${jwt.secret}")String secret) {
        this.JWT_SECRET_KEY = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm());
    }

    /**
     * @param category  토큰의 카테고리 정보 (Authorization, refresh)
     * @param mbrId 사용자 ID
     * @param lgnId 로그인 ID
     * @param mbrNm 사용자 이름
     * @param roles  사용자 권한 목록
     * @param expiredMs 토큰 만료 시간 (밀리초)
     * @return 생성된 JWT 토큰 (String)
     *
     * JWT 토큰 생성
     */
    public String createJwt(String category, String mbrId, String lgnId, String mbrNm, List<String> roles, long expiredMs) {
        return Jwts.builder()
                .claim("category", category)
                .claim("mbrId", mbrId)
                .claim("lgnId", lgnId)
                .claim("mbrNm", mbrNm)
                .claim("roles", roles)
                .issuedAt(new Date(System.currentTimeMillis())) // 토큰 발행 시간
                .expiration(new Date(System.currentTimeMillis() + expiredMs))   // 토큰 소멸 시간
                .signWith(JWT_SECRET_KEY)
                .compact();
    }

    /**
     * @param key 쿠키 키 값
     * @param value 쿠키 값
     * @param time 쿠키의 생명주기 (초 단위)
     * @return 생성된 Cookie 객체
     *
     * 쿠키 생성
     */
    public Cookie createCookie(String key, String value, int time) {
        // 쿠키 생성
        Cookie cookie = new Cookie(key, value);
        cookie.setMaxAge(time); // 생명주기
        //cookie.setSecure(true); // https 통신을 할 경우 true로 사용
        cookie.setPath("/"); // 쿠키 적용 범위
        cookie.setHttpOnly(true); // front에서 script로 접근 방지
        return cookie;
    }
    // 로그인 사용자 아이디 조회
    public String getWriter() {
        String mbrId = null;
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if(authentication != null && authentication.isAuthenticated()) {
            Object principal = authentication.getPrincipal();
            if(principal instanceof UserDetails) mbrId = ((MberVO) authentication.getPrincipal()).getMbrId();
        }
        return mbrId;
    }

    /**
     * @param tkn JWT 토큰 문자열
     * @param knd 조회할 데이터의 종류 (예: ctgry, userId, loginId 등)
     * @return 조회된 클레임 데이터 (종류에 따라 String, Date, List 등으로 반환)
     * @throws IllegalArgumentException 유효하지 않은 knd 값일 경우 예외 발생
     *
     * 클레임 조회
     */
    public Object getClaim(String tkn, String knd) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .verifyWith(JWT_SECRET_KEY)
                    .build()
                    .parseSignedClaims(tkn)
                    .getPayload();
        } catch (ExpiredJwtException e) {
            // 만료된 토큰이라도 claims 꺼내기 가능
            claims = e.getClaims();
        } catch (JwtException | IllegalArgumentException e) {
            // 토큰 자체가 잘못된 경우
            throw new IllegalArgumentException("Invalid token");
        }

        switch (knd) {
            case "category":
                return claims.get("category", String.class);
            case "mbrId":
                return claims.get("mbrId", String.class);
            case "lgnId":
                return claims.get("lgnId", String.class);
            case "mbrNm":
                return claims.get("mbrNm", String.class);
            case "roles":
                List<Map<String, Object>> roles = claims.get("roles", List.class);
                List<MberAuthorVO> authorList = new ArrayList<>();
                if (roles != null && !roles.isEmpty()) {
                    for (Map<String, Object> role : roles) {
                        MberAuthorVO userAuthor = new MberAuthorVO(role.get("authority").toString());
                        authorList.add(userAuthor);
                    }
                }
                return authorList;
            case "isExpired":
                return claims.getExpiration().before(new Date());
            case "expired":
                return claims.getExpiration();
            default:
                throw new IllegalArgumentException("Invalid knd : " + knd);
        }
    }

}
