package com.kt.event.common.security;

import com.kt.event.common.util.StringUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

/**
 * JWT 인증 필터
 * 요청 헤더에서 JWT 토큰을 추출하고 인증 처리
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    /**
     * Authorization 헤더 이름
     */
    private static final String AUTHORIZATION_HEADER = "Authorization";

    /**
     * Bearer 토큰 접두사
     */
    private static final String BEARER_PREFIX = "Bearer ";

    /**
     * JWT 토큰 제공자
     */
    private final JwtTokenProvider jwtTokenProvider;

    /**
     * 필터 실행
     *
     * @param request     HTTP 요청
     * @param response    HTTP 응답
     * @param filterChain 필터 체인
     * @throws ServletException 서블릿 예외
     * @throws IOException      입출력 예외
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        try {
            // 요청에서 JWT 토큰 추출
            String token = extractTokenFromRequest(request);

            // 토큰이 존재하고 유효한 경우 인증 처리
            if (StringUtil.isNotBlank(token) && jwtTokenProvider.validateToken(token)) {
                // Access Token인지 확인
                if (jwtTokenProvider.isAccessToken(token)) {
                    authenticateUser(token, request);
                } else {
                    log.warn("Refresh token used for authentication: {}", request.getRequestURI());
                }
            }
        } catch (Exception e) {
            log.error("Could not set user authentication in security context", e);
        }

        filterChain.doFilter(request, response);
    }

    /**
     * 요청에서 JWT 토큰 추출
     *
     * @param request HTTP 요청
     * @return JWT 토큰 (없으면 null)
     */
    private String extractTokenFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader(AUTHORIZATION_HEADER);

        if (StringUtil.isNotBlank(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)) {
            return bearerToken.substring(BEARER_PREFIX.length());
        }

        return null;
    }

    /**
     * 사용자 인증 처리
     *
     * @param token   JWT 토큰
     * @param request HTTP 요청
     */
    private void authenticateUser(String token, HttpServletRequest request) {
        // 토큰에서 사용자 정보 추출
        UserPrincipal userPrincipal = jwtTokenProvider.getUserPrincipalFromToken(token);

        // Spring Security 인증 객체 생성
        UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken(
                        userPrincipal,
                        null,
                        userPrincipal.getAuthorities()
                );

        // 요청 상세 정보 설정
        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

        // SecurityContext에 인증 정보 저장
        SecurityContextHolder.getContext().setAuthentication(authentication);

        log.debug("Set authentication for user: {} (userId: {})",
                userPrincipal.getEmail(), userPrincipal.getUserId());
    }

    /**
     * 필터 적용 여부 결정
     * OPTIONS 요청은 필터를 적용하지 않음
     *
     * @param request HTTP 요청
     * @return 필터 적용 제외 여부
     */
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        return "OPTIONS".equalsIgnoreCase(request.getMethod());
    }
}
