package com.spring.infra.security.error;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.security.SignatureException;
import lombok.RequiredArgsConstructor;

/**
 * 보안 관련 예외를 처리하는 클래스입니다.
 * 
 * <p>이 클래스는 Spring의 @RestControllerAdvice를 사용하여
 * 보안 관련 예외를 전역적으로 처리합니다.</p>
 * 
 * <p>각 예외에 대해 적절한 응답을 생성하여 클라이언트에 반환합니다.</p>
 * 
 * @author mindol
 * @version 1.0
 */
@RestControllerAdvice
@RequiredArgsConstructor
public class SecurityExceptionHandler {

    /**
     * 접근 거부 핸들러.
     * 
     * <p>AccessDeniedException이 발생했을 때 호출되며,
     * 사용자에게 접근 거부 응답을 반환합니다.</p>
     */
    private final AccessDeniedHandler accessDeniedHandler;

    /**
     * 사용자 인증 예외를 처리합니다.
     * 
     * <p>SecurityAuthException이 발생했을 때 호출되며,
     * 해당 예외에 대한 응답을 생성합니다.</p>
     * 
     * @param e 발생한 SecurityAuthException
     * @return SecurityErrorResponse 인증 오류에 대한 응답
     */
    @ExceptionHandler(SecurityAuthException.class)
    public SecurityErrorResponse handleAuthenticationException(SecurityAuthException e) {
        return SecurityErrorResponse.valueOf(e.getExceptionRule());
    }

    /**
     * 인증 예외를 처리합니다.
     * 
     * <p>AuthenticationException이 발생했을 때 호출되며,
     * 사용자 인증이 실패했음을 나타내는 응답을 생성합니다.</p>
     * 
     * @return SecurityErrorResponse 사용자 인증 실패에 대한 응답
     */
    @ExceptionHandler(AuthenticationException.class)
    public SecurityErrorResponse handleAuthenticationException() {
        return SecurityErrorResponse.valueOf(SecurityExceptionRule.USER_UNAUTHORIZED);
    }

    /**
     * 서명 예외를 처리합니다.
     * 
     * <p>SignatureException이 발생했을 때 호출되며,
     * 서명 오류에 대한 응답을 생성합니다.</p>
     * 
     * @return SecurityErrorResponse 서명 오류에 대한 응답
     */
    @ExceptionHandler(SignatureException.class)
    public SecurityErrorResponse handleSignatureException() {
        return SecurityErrorResponse.valueOf(SecurityExceptionRule.SIGNATURE_ERROR);
    }

    /**
     * 잘못된 JWT 예외를 처리합니다.
     * 
     * <p>MalformedJwtException이 발생했을 때 호출되며,
     * 잘못된 JWT에 대한 응답을 생성합니다.</p>
     * 
     * @return SecurityErrorResponse 잘못된 JWT 오류에 대한 응답
     */
    @ExceptionHandler(MalformedJwtException.class)
    public SecurityErrorResponse handleMalformedJwtException() {
        return SecurityErrorResponse.valueOf(SecurityExceptionRule.MALFORMED_JWT_ERROR);
    }

    /**
     * 만료된 JWT 예외를 처리합니다.
     * 
     * <p>ExpiredJwtException이 발생했을 때 호출되며,
     * 만료된 JWT에 대한 응답을 생성합니다.</p>
     * 
     * @return SecurityErrorResponse 만료된 JWT 오류에 대한 응답
     */
    @ExceptionHandler(ExpiredJwtException.class)
    public SecurityErrorResponse handleExpiredJwtException() {
        return SecurityErrorResponse.valueOf(SecurityExceptionRule.EXPIRED_JWT_ERROR);
    }

    /**
     * 접근 거부 예외를 처리합니다.
     * 
     * <p>AccessDeniedException이 발생했을 때 호출되며,
     * 접근 거부 핸들러를 사용하여 적절한 응답을 반환합니다.</p>
     * 
     * @param ex 발생한 AccessDeniedException
     * @param request HTTP 요청 객체
     * @param response HTTP 응답 객체
     * @throws IOException 입출력 예외
     * @throws ServletException 서블릿 예외
     */
    @ExceptionHandler(AccessDeniedException.class)
    public void handleAccessDeniedException(
        AccessDeniedException ex, HttpServletRequest request, HttpServletResponse response
    ) throws IOException, ServletException {
        accessDeniedHandler.handle(request, response, ex);
    }

}
