package com.takensoft.common.exception;

import com.takensoft.common.message.MessageCode;
import com.takensoft.common.util.ResponseUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.resource.NoResourceFoundException;

import java.net.UnknownHostException;

/**
 * @author takensoft
 * @since 2025.01.22
 * @modification
 *     since    |    author    | description
 *  2025.01.22  |  takensoft   | 최초 등록
 *  2025.03.12  |    하석형     | handleCustomCodeDuplicationException, handleCustomDataDuplicationException 추가
 *  2025.03.21  |    하석형     | handleFileSizeLimitExceededException, handleCustomFileUploadFailException, handleCustomPrhibtWordException 추가
 *
 * 스프링 MVC 컨트롤러에서 발생하는 예외를 처리하는 공통 클래스
 */
@RestControllerAdvice
@Slf4j
@RequiredArgsConstructor
public class GlobalExceptionHandler {

    private final ResponseUtil resUtil;


    /**
     * @param e - 처리할 예외 객체
     *
     * 예외를 로그로 출력
     */
    private void logError(Exception e) {
        StackTraceElement[] stackTrace = e.getStackTrace();
        if(stackTrace.length > 0) {
            StackTraceElement origin = stackTrace[0]; // 예외가 발생한 첫 번째 위치
            log.error("[ {} ] - {} ({} [{}]번째 행)",
                    e.getClass().getSimpleName(),
                    e.getMessage(),
                    origin.getFileName(),
                    origin.getLineNumber()
            );
        } else {
            log.error("[ {} ] - {}", e.getClass().getSimpleName(), e.getMessage());
        }
        e.printStackTrace();
    }

    /**
     * @param dae - DataAccessException 예외 객체
     * @return 유효성 검사 실패에 대한 HTTP 응답
     *
     * SQL 예외 처리
     */
    @ExceptionHandler(DataAccessException.class)
    public ResponseEntity<?> handleDataAccessException(DataAccessException dae) {
        logError(dae);
//        String msg = dae.getMessage().toLowerCase();
//        if(msg.contains("null value")) {
//            return resUtil.errorRes(MessageCode.SQL_NULL_VALUE);
//        } else if(msg.contains("duplicate key")) {
//            return resUtil.errorRes(MessageCode.SQL_DUPLICATE_KEY);
//        }
        return resUtil.errorRes(MessageCode.COMMON_UNKNOWN_ERROR);
    }

    /**
     * @param mave - MethodArgumentNotValidException 예외 객체
     * @return 유효성 검사 실패에 대한 HTTP 응답
     *
     * 유효성 검증에 실패한 경우
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<?> handleValidationException(MethodArgumentNotValidException mave) {
        logError(mave);
        String message = mave.getBindingResult().getFieldErrors().stream()
                .findFirst()
                .map(error -> error.getDefaultMessage())
                .orElse("유효성 검증에 실패했습니다.");
        return resUtil.errorRes(HttpStatus.BAD_REQUEST, message);
    }

    /**
     * @param ne - NullPointerException 예외 객체
     * @return NullPointerException에 대한 HTTP 응답
     *
     * NullPointerException이 발생한 경우
     */
    @ExceptionHandler(NullPointerException.class)
    public ResponseEntity<?> handleNullPointerException(NullPointerException ne) {
        logError(ne);
        return resUtil.errorRes(MessageCode.COMMON_NULL_POINT);
    }

    /**
     * @param ie - IllegalArgumentException 예외 객체
     * @return IllegalArgumentException에 대한 HTTP 응답
     *
     * IllegalArgumentException이 발생한 경우
     */
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<?> handleIllegalArgumentException(IllegalArgumentException ie) {
        logError(ie);
        return resUtil.errorRes(MessageCode.COMMON_ILLEGAL_ARGUMENT);
    }

    /**
     * @param nrfe - NoResourceFoundException 예외 객체
     * @return NoResourceFoundException에 대한 HTTP 응답
     *
     * NoResourceFoundException이 발생한 경우
     */
    @ExceptionHandler(NoResourceFoundException.class)
    public ResponseEntity<?> handleNoResourceFoundException(NoResourceFoundException nrfe) {
        logError(nrfe);
        return resUtil.errorRes(MessageCode.COMMON_BAD_REQUEST);
    }

    /**
     * @param hrmnse - HttpRequestMethodNotSupportedException 예외 객체
     * @return HttpRequestMethodNotSupportedException에 대한 HTTP 응답
     *
     * HttpRequestMethodNotSupportedException이 발생한 경우
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ResponseEntity<?> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException hrmnse) {
        logError(hrmnse);
        return resUtil.errorRes(MessageCode.COMMON_METHOD_NOT_ALLOWED);
    }

    /**
     * @param cite - CustomIdTakenException 예외 객체
     * @return CustomIdTakenException에 대한 HTTP 응답
     *
     * CustomIdTakenException이 발생한 경우
     */
    @ExceptionHandler(CustomIdTakenException.class)
    public ResponseEntity<?> handleCustomIdTakenException(CustomIdTakenException cite) {
        logError(cite);
        return resUtil.errorRes(MessageCode.SIGNUP_ID_TAKEN);
    }

    /**
     * @param cbre - CustomBadRequestException 예외 객체
     * @return CustomBadRequestException에 대한 HTTP 응답
     *
     * CustomBadRequestException이 발생한 경우
     */
    @ExceptionHandler(CustomBadRequestException.class)
    public ResponseEntity<?> handleCustomBadRequestException(CustomBadRequestException cbre) {
        logError(cbre);
        return resUtil.errorRes(MessageCode.COMMON_BAD_REQUEST);
    }

    /**
     * @param cade - CustomAccessDeniedException 예외 객체
     * @return CustomAccessDeniedException에 대한 HTTP 응답
     *
     * CustomAccessDeniedException이 발생한 경우
     */
    @ExceptionHandler(CustomAccessDeniedException.class)
    public ResponseEntity<?> handleCustomAccessDeniedException(CustomAccessDeniedException cade) {
        logError(cade);
        return resUtil.errorRes(MessageCode.ACCESS_DENIED);
    }

    /**
     * @param cpce - CustomPasswordComparisonException 예외 객체
     * @return CustomPasswordComparisonException에 대한 HTTP 응답
     *
     * CustomPasswordComparisonException이 발생한 경우
     */
    @ExceptionHandler(CustomPasswordComparisonException.class)
    public ResponseEntity<?> handleCustomPasswordComparisonException(CustomPasswordComparisonException cpce) {
        logError(cpce);
        return resUtil.errorRes(MessageCode.LOGIN_INVALID_CREDENTIALS);
    }

    /**
     * @param cfe - CustomNotFoundException 예외 객체
     * @return CustomNotFoundException에 대한 HTTP 응답
     *
     * CustomNotFoundException이 발생한 경우
     */
    @ExceptionHandler(CustomNotFoundException.class)
    public ResponseEntity<?> handleNotFoundException(CustomNotFoundException cfe) {
        logError(cfe);
        return resUtil.errorRes(MessageCode.COMMON_NOT_FOUND);
    }

    /**
     * @param cife - CustomInsertFailException 예외 객체
     * @return CustomInsertFailException에 대한 HTTP 응답
     *
     * CustomInsertFailException이 발생한 경우
     */
    @ExceptionHandler(CustomInsertFailException.class)
    public ResponseEntity<?> handleCustomInsertFailException(CustomInsertFailException cife) {
        logError(cife);
        return resUtil.errorRes(MessageCode.COMMON_INSERT_FAIL);
    }

    /**
     * @param cufe - CustomUpdateFailException 예외 객체
     * @return CustomUpdateFailException에 대한 HTTP 응답
     *
     * CustomUpdateFailException이 발생한 경우
     */
    @ExceptionHandler(CustomUpdateFailException.class)
    public ResponseEntity<?> handleCustomUpdateFailException(CustomUpdateFailException cufe) {
        logError(cufe);
        return resUtil.errorRes(MessageCode.COMMON_UPDATE_FAIL);
    }

    /**
     * @param cdfe - CustomDeleteFailException 예외 객체
     * @return CustomDeleteFailException에 대한 HTTP 응답
     *
     * CustomDeleteFailException이 발생한 경우
     */
    @ExceptionHandler(CustomDeleteFailException.class)
    public ResponseEntity<?> handleCustomDeleteFailException(CustomDeleteFailException cdfe) {
        logError(cdfe);
        return resUtil.errorRes(MessageCode.COMMON_DELETE_FAIL);
    }

    /**
     * @param ukhe - UnknownHostException 예외 객체
     * @return UnknownHostException에 대한 HTTP 응답
     *
     * UnknownHostException이 발생한 경우
     */
    @ExceptionHandler(UnknownHostException.class)
    public ResponseEntity<?> handleUnknownHostException(UnknownHostException ukhe) {
        logError(ukhe);
        return resUtil.errorRes(MessageCode.NETWORK_UNKNOWN_HOST);
    }

    /**
     * @param ccde - CustomCodeDuplicationException 예외 객체
     * @return CustomCodeDuplicationException에 대한 HTTP 응답
     *
     * CustomCodeDuplicationException이 발생한 경우
     */
    @ExceptionHandler(CustomCodeDuplicationException.class)
    public ResponseEntity<?> handleCustomCodeDuplicationException(CustomCodeDuplicationException ccde) {
        logError(ccde);
        return resUtil.errorRes(MessageCode.COMMON_DUPLICATION_CODE);
    }

    /**
     * @param cdde - CustomDataDuplicationException 예외 객체
     * @return CustomDataDuplicationException에 대한 HTTP 응답
     *
     * CustomDataDuplicationException이 발생한 경우
     */
    @ExceptionHandler(CustomDataDuplicationException.class)
    public ResponseEntity<?> handleCustomDataDuplicationException(CustomDataDuplicationException cdde) {
        logError(cdde);
        // CustomDataDuplicationException의 커스텀 메시지가 "Y"인 경우
        if(cdde.getCustom().equals("Y")) {
            return resUtil.errorRes(HttpStatus.INTERNAL_SERVER_ERROR, cdde.getMessage());
        } else {
            return resUtil.errorRes(MessageCode.COMMON_DUPLICATION_DATA);
        }
    }

    /**
     * @param fsle - FileSizeLimitExceededException 예외 객체
     * @return FileSizeLimitExceededException에 대한 HTTP 응답
     *
     * FileSizeLimitExceededException이 발생한 경우
     */
    @ExceptionHandler(FileSizeLimitExceededException.class)
    public ResponseEntity<?> handleFileSizeLimitExceededException(FileSizeLimitExceededException fsle) {
        logError(fsle);
        return resUtil.errorRes(MessageCode.COMMON_PAYLOAD_TOO_LARGE);
    }

    /**
     * @param cfufe - CustomFileUploadFailException 예외 객체
     * @return CustomFileUploadFailException에 대한 HTTP 응답
     *
     * CustomFileUploadFailException이 발생한 경우
     */
    @ExceptionHandler(CustomFileUploadFailException.class)
    public ResponseEntity<?> handleCustomFileUploadFailException(CustomFileUploadFailException cfufe) {
        logError(cfufe);
        return resUtil.errorRes(MessageCode.FILE_UPLOAD_FAIL);
    }

    /**
     * @param cpwe - CustomPrhibtWordException 예외 객체
     * @return CustomPrhibtWordException에 대한 HTTP 응답
     *
     * CustomPrhibtWordException이 발생한 경우
     */
    @ExceptionHandler(CustomPrhibtWordException.class)
    public ResponseEntity<?> handleCustomPrhibtWordException(CustomPrhibtWordException cpwe) {
        logError(cpwe);
        return resUtil.errorRes(MessageCode.COMMON_PROHIBITION_WORD, "\n* " + cpwe.getWord());
    }

    /**
     * @param cnce - CustomNoChangeException 예외 객체
     * @return CustomNoChangeException에 대한 HTTP 응답
     *
     * CustomNoChangeException이 발생한 경우
     */
    @ExceptionHandler(CustomNoChangeException.class)
    public ResponseEntity<?> handleCustomInsertFailException(CustomNoChangeException cnce) {
        logError(cnce);
        return resUtil.errorRes(MessageCode.COMMON_NO_CHANGE);
    }

    /**
     * @param cesfe - CustomEmailSendFailException 예외 객체
     * @return CustomEmailSendFailException에 대한 HTTP 응답
     *
     * CustomEmailSendFailException이 발생한 경우
     */
    @ExceptionHandler(CustomEmailSendFailException.class)
    public ResponseEntity<?> handleCustomEmailSendFailException(CustomEmailSendFailException cesfe) {
        logError(cesfe);
        return resUtil.errorRes(MessageCode.EMAIL_SEND_FAIL);
    }

    /**
     * @param cevee - CustomEmailVerifyExpireException 예외 객체
     * @return CustomEmailVerifyExpireException에 대한 HTTP 응답
     *
     * CustomEmailVerifyExpireException이 발생한 경우
     */
    @ExceptionHandler(CustomEmailVerifyExpireException.class)
    public ResponseEntity<?> handleCustomEmailVerifyExpireException(CustomEmailVerifyExpireException cevee) {
        logError(cevee);
        return resUtil.errorRes(MessageCode.EMAIL_VERIFY_EXPIRED);
    }

    /**
     * @param cevfe - CustomEmailVerifyFailException 예외 객체
     * @return CustomEmailVerifyFailException에 대한 HTTP 응답
     *
     * CustomEmailVerifyFailException이 발생한 경우
     */
    @ExceptionHandler(CustomEmailVerifyFailException.class)
    public ResponseEntity<?> handleCustomEmailVerifyFailException(CustomEmailVerifyFailException cevfe) {
        logError(cevfe);
        return resUtil.errorRes(MessageCode.EMAIL_VERIFY_FAIL);
    }

    /**
     * @param cecnme - CustomEmailCodeNotMatchException 예외 객체
     * @return CustomEmailCodeNotMatchException에 대한 HTTP 응답
     *
     * CustomEmailCodeNotMatchException이 발생한 경우
     */
    @ExceptionHandler(CustomEmailCodeNotMatchException.class)
    public ResponseEntity<?> handleCustomEmailCodeNotMatchException(CustomEmailCodeNotMatchException cecnme) {
        logError(cecnme);
        return resUtil.errorRes(MessageCode.CODE_NOT_MATCH);
    }

    /**
     * @param cete - CustomEmailTakenException 예외 객체
     * @return CustomEmailTakenException에 대한 HTTP 응답
     *
     * CustomEmailTakenException이 발생한 경우
     */
    @ExceptionHandler(CustomEmailTakenException.class)
    public ResponseEntity<?> handleCustomEmailTakenException(CustomEmailTakenException cete) {
        logError(cete);
        return resUtil.errorRes(MessageCode.SIGNUP_EMAIL_TAKEN);
    }

    /**
     * @param e - Exception 예외 객체
     * @return 기타 예외에 대한 HTTP 응답
     *
     * 그 외 모든 예외가 발생한 경우
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<?> handleException(Exception e) {
        logError(e);
        return resUtil.errorRes(MessageCode.COMMON_UNKNOWN_ERROR);
    }
}
