package org.qinan.safetyeval.adapter.handler;

import org.qinan.safetyeval.client.dto.SingleResponse;
import org.qinan.safetyeval.domain.exception.BizException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.http.converter.HttpMessageNotReadableException;

import javax.validation.ConstraintViolationException;

@RestControllerAdvice(basePackages = "org.qinan.safetyeval.adapter.web")
public class GlobalExceptionHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    private static final String VALIDATION_ERROR_CODE = "400";
    private static final String SYSTEM_ERROR_CODE = "500";

    private final MessageSource messageSource;

    public GlobalExceptionHandler(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    @ExceptionHandler(BizException.class)
    public ResponseEntity<SingleResponse<Void>> handleBizException(BizException exception) {
        String message = message("error." + exception.getCode(), exception.getMessage());
        return response(HttpStatus.OK, exception.getCode(), message);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<SingleResponse<Void>> handleMethodArgumentNotValid(
            MethodArgumentNotValidException exception) {
        FieldError fieldError = exception.getBindingResult().getFieldError();
        String fallback = fieldError == null ? null : fieldError.getDefaultMessage();
        return response(HttpStatus.OK, VALIDATION_ERROR_CODE,
                message("validation.failed", fallback));
    }

    @ExceptionHandler(BindException.class)
    public ResponseEntity<SingleResponse<Void>> handleBindException(BindException exception) {
        FieldError fieldError = exception.getBindingResult().getFieldError();
        String fallback = fieldError == null ? null : fieldError.getDefaultMessage();
        return response(HttpStatus.OK, VALIDATION_ERROR_CODE,
                message("validation.failed", fallback));
    }

    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<SingleResponse<Void>> handleConstraintViolation(
            ConstraintViolationException exception) {
        return response(HttpStatus.OK, VALIDATION_ERROR_CODE,
                message("validation.failed", exception.getMessage()));
    }

    @ExceptionHandler({HttpMessageNotReadableException.class, MissingServletRequestParameterException.class})
    public ResponseEntity<SingleResponse<Void>> handleInvalidRequest(Exception exception) {
        return response(HttpStatus.OK, VALIDATION_ERROR_CODE,
                message("request.invalid", exception.getMessage()));
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<SingleResponse<Void>> handleUnexpectedException(Exception exception) {
        LOGGER.error("Unhandled controller exception", exception);
        return response(HttpStatus.OK, SYSTEM_ERROR_CODE,
                message("system.error", "Internal server error"));
    }

    private ResponseEntity<SingleResponse<Void>> response(HttpStatus status, String code, String message) {
        return ResponseEntity.status(status).body(SingleResponse.error(code, message));
    }

    private String message(String code, String fallback) {
        return messageSource.getMessage(code, null, fallback, LocaleContextHolder.getLocale());
    }
}
