package com.kt.event.common.exception;

import com.kt.event.common.dto.ErrorResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.mapping.PropertyReferenceException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.List;
import java.util.stream.Collectors;

/**
 * 전역 예외 핸들러
 * 애플리케이션 전체에서 발생하는 예외를 일관된 형식으로 처리
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 비즈니스 예외 처리
     *
     * @param ex 비즈니스 예외
     * @return 에러 응답
     */
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) {
        log.warn("Business exception occurred: {}", ex.getMessage());

        ErrorResponse errorResponse = ErrorResponse.of(
                ex.getErrorCode().getCode(),
                ex.getMessage(),
                ex.getDetails()
        );

        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(errorResponse);
    }

    /**
     * 인프라 예외 처리
     *
     * @param ex 인프라 예외
     * @return 에러 응답
     */
    @ExceptionHandler(InfraException.class)
    public ResponseEntity<ErrorResponse> handleInfraException(InfraException ex) {
        log.error("Infrastructure exception occurred: {}", ex.getMessage(), ex);

        ErrorResponse errorResponse = ErrorResponse.of(
                ex.getErrorCode().getCode(),
                ex.getMessage(),
                ex.getDetails()
        );

        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(errorResponse);
    }

    /**
     * 인증 예외 처리
     *
     * @param ex 인증 예외
     * @return 에러 응답
     */
    @ExceptionHandler(AuthenticationException.class)
    public ResponseEntity<ErrorResponse> handleAuthenticationException(AuthenticationException ex) {
        log.warn("Authentication exception occurred: {}", ex.getMessage());

        ErrorResponse errorResponse = ErrorResponse.of(
                ErrorCode.AUTH_001.getCode(),
                ErrorCode.AUTH_001.getMessage(),
                ex.getMessage()
        );

        return ResponseEntity
                .status(HttpStatus.UNAUTHORIZED)
                .body(errorResponse);
    }

    /**
     * 권한 예외 처리
     *
     * @param ex 권한 예외
     * @return 에러 응답
     */
    @ExceptionHandler(AccessDeniedException.class)
    public ResponseEntity<ErrorResponse> handleAccessDeniedException(AccessDeniedException ex) {
        log.warn("Access denied exception occurred: {}", ex.getMessage());

        ErrorResponse errorResponse = ErrorResponse.of(
                ErrorCode.AUTH_004.getCode(),
                ErrorCode.AUTH_004.getMessage(),
                ex.getMessage()
        );

        return ResponseEntity
                .status(HttpStatus.FORBIDDEN)
                .body(errorResponse);
    }

    /**
     * 유효성 검증 예외 처리 (RequestBody)
     *
     * @param ex 유효성 검증 예외
     * @return 에러 응답
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        log.warn("Validation exception occurred: {}", ex.getMessage());

        List<ErrorResponse.FieldError> fieldErrors = ex.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(this::mapToFieldError)
                .collect(Collectors.toList());

        ErrorResponse errorResponse = ErrorResponse.of(
                ErrorCode.COMMON_003.getCode(),
                ErrorCode.COMMON_003.getMessage(),
                fieldErrors
        );

        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(errorResponse);
    }

    /**
     * 유효성 검증 예외 처리 (ModelAttribute)
     *
     * @param ex 유효성 검증 예외
     * @return 에러 응답
     */
    @ExceptionHandler(BindException.class)
    public ResponseEntity<ErrorResponse> handleBindException(BindException ex) {
        log.warn("Bind exception occurred: {}", ex.getMessage());

        List<ErrorResponse.FieldError> fieldErrors = ex.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(this::mapToFieldError)
                .collect(Collectors.toList());

        ErrorResponse errorResponse = ErrorResponse.of(
                ErrorCode.COMMON_003.getCode(),
                ErrorCode.COMMON_003.getMessage(),
                fieldErrors
        );

        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(errorResponse);
    }

    /**
     * 데이터 무결성 제약 위반 예외 처리
     *
     * @param ex 데이터 무결성 예외
     * @return 에러 응답
     */
    @ExceptionHandler(DataIntegrityViolationException.class)
    public ResponseEntity<ErrorResponse> handleDataIntegrityViolationException(DataIntegrityViolationException ex) {
        log.error("=== DataIntegrityViolationException 발생 ===");
        log.error("Exception type: {}", ex.getClass().getSimpleName());
        log.error("Exception message: {}", ex.getMessage());
        log.error("Root cause: {}", ex.getRootCause() != null ? ex.getRootCause().getMessage() : "null");
        log.error("Stack trace: ", ex);

        String message = "데이터 중복 또는 무결성 제약 위반이 발생했습니다";
        String details = ex.getMessage();

        // 중복 키 에러인 경우 메시지 개선
        if (ex.getMessage() != null) {
            if (ex.getMessage().contains("uk_event_phone") || ex.getMessage().contains("phone_number")) {
                message = "이미 참여하신 이벤트입니다";
                details = "동일한 전화번호로 이미 참여 기록이 있습니다";
            } else if (ex.getMessage().contains("participant_id")) {
                message = "참여 처리 중 오류가 발생했습니다";
                details = "잠시 후 다시 시도해주세요";
            }
        }

        ErrorResponse errorResponse = ErrorResponse.of(
                ErrorCode.DUPLICATE_PARTICIPATION.getCode(),
                message,
                details
        );

        return ResponseEntity
                .status(HttpStatus.CONFLICT)
                .body(errorResponse);
    }

    /**
     * 잘못된 정렬 필드 예외 처리
     *
     * @param ex 속성 참조 예외
     * @return 에러 응답
     */
    @ExceptionHandler(PropertyReferenceException.class)
    public ResponseEntity<ErrorResponse> handlePropertyReferenceException(PropertyReferenceException ex) {
        log.warn("Invalid sort property: {}", ex.getMessage());

        String message = "잘못된 정렬 필드입니다";
        String details = String.format("'%s' 필드는 존재하지 않습니다. 사용 가능한 필드: id, participantId, eventId, name, phoneNumber, email, storeVisited, bonusEntries, agreeMarketing, agreePrivacy, isWinner, winnerRank, wonAt, createdAt, updatedAt",
                ex.getPropertyName());

        ErrorResponse errorResponse = ErrorResponse.of(
                ErrorCode.COMMON_003.getCode(),
                message,
                details
        );

        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(errorResponse);
    }

    /**
     * 일반 예외 처리
     *
     * @param ex 일반 예외
     * @return 에러 응답
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleException(Exception ex) {
        log.error("Unexpected exception occurred: {}", ex.getMessage(), ex);

        ErrorResponse errorResponse = ErrorResponse.of(
                ErrorCode.COMMON_004.getCode(),
                ErrorCode.COMMON_004.getMessage(),
                ex.getMessage()
        );

        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(errorResponse);
    }

    /**
     * Spring FieldError를 ErrorResponse.FieldError로 변환
     *
     * @param fieldError Spring FieldError
     * @return ErrorResponse.FieldError
     */
    private ErrorResponse.FieldError mapToFieldError(FieldError fieldError) {
        return ErrorResponse.FieldError.builder()
                .field(fieldError.getField())
                .rejectedValue(fieldError.getRejectedValue())
                .message(fieldError.getDefaultMessage())
                .build();
    }
}
