package egovframework.exception;

import egovframework.configProperties.LoginProperties;
import egovframework.util.ApiResponseEntity;
import egovframework.util.HttpServletUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.TransactionException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * @ControllerAdvice를 사용한 전역 예외 처리기
 * 이는 SimpleMappingExceptionResolver 접근 방식을 대체합니다
 * 
 * AJAX 요청(*.ajax)에 대해서는 JSON 형식의 예외 응답을 반환하고,
 * 일반 요청에 대해서는 egovError.jsp 페이지로 리다이렉트합니다.
 */
@Slf4j
@ControllerAdvice
@RequiredArgsConstructor
public class EgovExceptionAdvice {

    private final LoginProperties loginProperties;

    /**
     * DataAccessException 예외 처리
     * 데이터 액세스 관련 예외를 처리합니다.
     */
    @ExceptionHandler(DataAccessException.class)
    public Object handleDataAccessException(DataAccessException e, HttpServletRequest request) {
        log.error("DataAccessException 발생: ", e);
        return getModelAndView(e, request, HttpStatus.BAD_REQUEST);
    }

    /**
     * TransactionException 예외 처리
     * 트랜잭션 관련 예외를 처리합니다.
     */
    @ExceptionHandler(TransactionException.class)
    public Object handleTransactionException(TransactionException e, HttpServletRequest request) {
        log.error("TransactionException 발생: ", e);
        return getModelAndView(e, request, HttpStatus.BAD_REQUEST);
    }

    /**
     * 커스텀 AccessDeniedException 예외 처리
     * 접근 권한이 없는 경우 발생하는 예외를 처리합니다.
     */
    @ExceptionHandler(AccessDeniedException.class)
    public Object handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) {
        log.warn("접근 권한 예외 발생 - 사용자: {}, URI: {}", e.getUserAcnt(), e.getRequestURI());
        return getModelAndView(e, request, HttpStatus.FORBIDDEN);
    }

    /**
     * 세션 만료 관련 예외 처리
     * HttpSessionRequiredException이 없으므로 일반적인 예외 처리로 대체합니다.
     * 이 메서드는 AuthInterceptor에서 세션 만료 시 AJAX 요청에 대한 처리를 보완합니다.
     */

    /**
     * RuntimeException 예외 처리
     * 런타임 예외를 처리합니다.
     */
    @ExceptionHandler(RuntimeException.class)
    public Object handleRuntimeException(RuntimeException e, HttpServletRequest request) {
        log.error("RuntimeException 발생: ", e);
        return getModelAndView(e, request, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    /**
     * Exception 예외 처리
     * 일반 예외를 처리합니다.
     */
    @ExceptionHandler(Exception.class)
    public Object handleException(Exception e, HttpServletRequest request) {
        log.error("Exception 발생: ", e);
        return getModelAndView(e, request, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    /**
     * IOException 예외 처리
     * IO 예외를 처리합니다.
     */
    @ExceptionHandler(IOException.class)
    public Object handleIOException(Exception e, HttpServletRequest request) {
        log.error("IOException 발생: ", e);
        return getModelAndView(e, request, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    /**
     * Throwable 예외 처리
     * 모든 예외의 상위 클래스인 Throwable을 처리합니다.
     */
    @ExceptionHandler(Throwable.class)
    public Object handleThrowable(Throwable e, HttpServletRequest request) {
        log.error("Throwable 발생: ", e);
        return getModelAndView(e, request, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    /**
     * MessageException 예외 처리
     * 메시지 관련 예외를 처리합니다.
     */
    @ExceptionHandler(MessageException.class)
    public Object handleMessageException(MessageException e, HttpServletRequest request) {
        log.warn("MessageException 발생: ", e);
        return getModelAndView(e, request, HttpStatus.BAD_REQUEST);
    }

    /**
     * 응답 객체를 생성하는 헬퍼 메소드
     * AJAX 요청인 경우 ResponseEntity<ApiResponse<Void>>를 반환하고, 
     * 일반 요청인 경우 egovError 페이지로 리다이렉트하는 ModelAndView를 반환합니다.
     * AJAX 요청 판단은 URL 패턴(*.ajax) 또는 HTTP 헤더 정보를 기반으로 합니다.
     * 
     * @param e 발생한 예외
     * @param request HTTP 요청 객체
     * @param status HTTP 상태 코드
     * @return AJAX 요청인 경우 ResponseEntity, 일반 요청인 경우 ModelAndView
     */
    private Object getModelAndView(Throwable e, HttpServletRequest request, HttpStatus status) {
        // AJAX 요청인 경우 ApiResponse를 사용한 JSON 응답 반환
        if (HttpServletUtil.isAjaxRequest(request) || HttpServletUtil.isRealAjaxRequest(request)) {
            ApiResponseEntity<Void> responseBody = ApiResponseEntity.<Void>builder()
                    .result(false)
                    .message(e.getMessage())
                    .errorCode(e.getClass().getSimpleName())
                    .build();
            return new ResponseEntity<>(responseBody, status);
        }

        // 일반 요청인 경우 egovError 페이지로 리다이렉트
        ModelAndView mav = new ModelAndView("error/egovError");
        mav.addObject("exceptionName", e.getClass().getSimpleName());
        mav.addObject("exception", e);
        return mav;
    }
}
