package com.face.global.exception;


import com.face.global.enums.CommonCode;
import com.face.global.language.LanguageStore;
import com.face.util.ApiResultBuilder;
import com.face.util.HttpUtils;
import com.face.util.MessageUtils;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.context.MessageSource;
import org.springframework.http.*;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.method.MethodValidationException;
import org.springframework.web.ErrorResponseException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.method.annotation.HandlerMethodValidationException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import org.springframework.web.servlet.resource.NoResourceFoundException;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@RestControllerAdvice
@Slf4j
public class GlobalErrorController extends ResponseEntityExceptionHandler {

    public GlobalErrorController(MessageSource messageSource) {
        this.setMessageSource(messageSource);
    }

    @ExceptionHandler(value = BusinessException.class)
    public ResponseEntity<Object> dealBusinessException(HttpServletRequest request, BusinessException ex) {
        this.printHttpLog(request, null, null, ex);
        return this.buildErrorResponseEntity(ex.getCode(), ex.getMessage());
    }


    @ExceptionHandler(value = Throwable.class)
    public ResponseEntity<Object> dealException(HttpServletRequest request, Throwable ex) {
        this.printHttpLog(request, null, null, ex);
        return this.buildErrorResponseEntity(CommonCode.ERROR.getCode(), null);
    }

    private ResponseEntity<Object> buildErrorResponseEntity(String code, String message) {
        String errorMessage = null;
        if (StringUtils.isBlank(message)) {
            errorMessage = MessageUtils.getMessage(code, null, LanguageStore.getLocale());
        }
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        return new ResponseEntity<>(ApiResultBuilder.getRawBuilder().baseInfo(code,
                Optional.ofNullable(errorMessage)
                        .orElse(message)).build(), headers, HttpStatus.OK);
    }

    @Override
    protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers,
                                                                         HttpStatusCode status, WebRequest request) {
        this.printLog(request, headers, status, ex);
        return this.buildErrorResponseEntity(CommonCode.ERROR.getCode(), ex.getMessage());
    }

    @Override
    protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        this.printLog(request, headers, status, ex);
        return this.buildErrorResponseEntity(CommonCode.ERROR.getCode(), ex.getMessage());
    }

    @Override
    protected ResponseEntity<Object> handleHttpMediaTypeNotAcceptable(HttpMediaTypeNotAcceptableException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        this.printLog(request, headers, status, ex);
        return this.buildErrorResponseEntity(CommonCode.ERROR.getCode(), ex.getMessage());
    }

    @Override
    protected ResponseEntity<Object> handleMissingPathVariable(MissingPathVariableException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        this.printLog(request, headers, status, ex);
        return this.buildErrorResponseEntity(CommonCode.ERROR.getCode(), ex.getMessage());
    }

    @Override
    protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        this.printLog(request, headers, status, ex);
        return this.buildErrorResponseEntity(CommonCode.ERROR.getCode(), ex.getMessage());
    }

    @Override
    protected ResponseEntity<Object> handleMissingServletRequestPart(MissingServletRequestPartException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        this.printLog(request, headers, status, ex);
        return this.buildErrorResponseEntity(CommonCode.ERROR.getCode(), ex.getMessage());
    }

    @Override
    protected ResponseEntity<Object> handleServletRequestBindingException(ServletRequestBindingException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        this.printLog(request, headers, status, ex);
        return this.buildErrorResponseEntity(CommonCode.ERROR.getCode(), ex.getMessage());
    }

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        this.printLog(request, headers, status, ex);
        List<String> datas = new ArrayList<>();
        if (ex.getDetailMessageArguments() == null || ex.getDetailMessageArguments().length <= 0) {
            return this.buildErrorResponseEntity(CommonCode.ERROR.getCode(), ex.getMessage());
        }
        for (Object obj : ex.getDetailMessageArguments()) {
            if (obj instanceof String && StringUtils.isNotBlank(obj.toString())) {
                if (obj.toString().contains(":")) {
                    datas.add(obj.toString().split(":")[1]);
                } else {
                    datas.add(obj.toString());
                }
            }
        }
        if (CollectionUtils.isEmpty(datas)) {
            return this.buildErrorResponseEntity(CommonCode.ERROR.getCode(), ex.getMessage());
        }
        return this.buildErrorResponseEntity(CommonCode.ERROR.getCode(), StringUtils.join(datas, ","));
    }

    @Override
    protected ResponseEntity<Object> handleHandlerMethodValidationException(HandlerMethodValidationException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        this.printLog(request, headers, status, ex);
        return this.buildErrorResponseEntity(CommonCode.ERROR.getCode(), ex.getMessage());
    }

    @Override
    protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        this.printLog(request, headers, status, ex);
        return this.buildErrorResponseEntity(CommonCode.ERROR.getCode(), ex.getMessage());
    }

    @Override
    protected ResponseEntity<Object> handleNoResourceFoundException(NoResourceFoundException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        this.printLog(request, headers, status, ex);
        return this.buildErrorResponseEntity(CommonCode.ERROR.getCode(), ex.getMessage());
    }

    @Override
    protected ResponseEntity<Object> handleAsyncRequestTimeoutException(AsyncRequestTimeoutException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        this.printLog(request, headers, status, ex);
        return this.buildErrorResponseEntity(CommonCode.ERROR.getCode(), ex.getMessage());
    }

    @Override
    protected ResponseEntity<Object> handleErrorResponseException(ErrorResponseException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        this.printLog(request, headers, status, ex);
        return this.buildErrorResponseEntity(CommonCode.ERROR.getCode(), ex.getMessage());
    }

    @Override
    protected ResponseEntity<Object> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        this.printLog(request, headers, status, ex);
        return this.buildErrorResponseEntity(CommonCode.ERROR.getCode(), ex.getMessage());
    }

    @Override
    protected ResponseEntity<Object> handleConversionNotSupported(ConversionNotSupportedException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        this.printLog(request, headers, status, ex);
        return this.buildErrorResponseEntity(CommonCode.ERROR.getCode(), ex.getMessage());
    }

    @Override
    protected ResponseEntity<Object> handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        this.printLog(request, headers, status, ex);
        return this.buildErrorResponseEntity(CommonCode.ERROR.getCode(), ex.getMessage());
    }

    @Override
    protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        this.printLog(request, headers, status, ex);
        return this.buildErrorResponseEntity(CommonCode.ERROR.getCode(), ex.getMessage());
    }

    @Override
    protected ResponseEntity<Object> handleHttpMessageNotWritable(HttpMessageNotWritableException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        this.printLog(request, headers, status, ex);
        return this.buildErrorResponseEntity(CommonCode.ERROR.getCode(), ex.getMessage());
    }

    @Override
    protected ResponseEntity<Object> handleMethodValidationException(MethodValidationException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        this.printLog(request, headers, status, ex);
        return this.buildErrorResponseEntity(CommonCode.ERROR.getCode(), ex.getMessage());
    }


    @Override
    protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatusCode statusCode, WebRequest request) {
        this.printLog(request, headers, statusCode, ex);
        return this.buildErrorResponseEntity(CommonCode.ERROR.getCode(), ex.getMessage());
    }

    private void printLog(WebRequest request, HttpHeaders headers, HttpStatusCode status, Throwable ex) {
        if (request instanceof HttpServletRequest) {
            this.printHttpLog(((HttpServletRequest) request), headers, status, ex);
        } else {
            log.error("requestUrlFail", ex);
        }
    }

    private void printHttpLog(HttpServletRequest request, HttpHeaders headers, HttpStatusCode status, Throwable ex) {
        log.error("requestUrlFail {} {} {} {}", request.getRequestURI(), request.getParameterMap(), HttpUtils.getHeadersInfo(request, headers),
                this.getStatusInfo(status),  ex);
    }

    private Integer getStatusInfo(HttpStatusCode status) {
        if (status != null) {
            return status.value();
        }
        return null;
    }
}
