package com.tofly.feesapi.common.exception;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.client.HttpClientErrorException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;

@Slf4j
@RestControllerAdvice
public class FeesErrorResponseHandler {

    @Autowired
    private ObjectMapper mapper;

    @ExceptionHandler(Exception.class)
    public void handle(Exception ex, HttpServletResponse response) {
        response.setCharacterEncoding("utf-8");
        if (!response.isCommitted()) {
            try {
                response.setContentType(MediaType.APPLICATION_JSON_VALUE);

                if (ex instanceof FeesException) {
                    FeesException FeesException = (FeesException) ex;
                    if (FeesException.getErrorCode() == FeesErrorCode.SUBSCRIPTION_VIOLATION) {
                        handleSubscriptionException((FeesException) ex, response);
                    } else {
                        handleFeesException((FeesException) ex, response);
                    }
                } else if (ex instanceof AccessDeniedException) {
                    handleAccessDeniedException(response);
                } else if (ex instanceof AuthenticationException) {
                    handleAuthenticationException((AuthenticationException) ex, response);
                } else if (ex instanceof MethodArgumentNotValidException) {
                    handleMethodArgumentNotValidException((MethodArgumentNotValidException) ex, response);
                } else if (ex instanceof HttpMessageNotReadableException) {
                    handleHttpMessageNotReadableException((HttpMessageNotReadableException) ex, response);
                } else if (ex instanceof ServletRequestBindingException) {
                    handleServletRequestBindingException((ServletRequestBindingException) ex, response);
                } else {
                    response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
                    log.error("**未知错误**", ex);
//                    mapper.writeValue(response.getWriter(), FeesErrorResponse.of("**未知错误**",
//                            FeesErrorCode.INTERNAL_SERVER_ERROR));
                    mapper.writeValue(response.getWriter(), FeesErrorResponse.of(ex.getMessage(),
                            FeesErrorCode.INTERNAL_SERVER_ERROR));
                }
            } catch (IOException e) {
                log.error("Can't handle exception", e);
            }
        }
    }

    private void handleServletRequestBindingException(ServletRequestBindingException ex, HttpServletResponse response) throws IOException {
        response.setStatus(HttpStatus.BAD_REQUEST.value());
        mapper.writeValue(response.getWriter(), FeesErrorResponse.of(ex.getMessage(),
                FeesErrorCode.BAD_REQUEST_PARAMS));
    }

    private void handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletResponse response) throws IOException {
        response.setStatus(HttpStatus.BAD_REQUEST.value());
        mapper.writeValue(response.getWriter(), FeesErrorResponse.of(Objects.requireNonNull(ex
                                .getBindingResult().getFieldError())
                        .getDefaultMessage(),
                FeesErrorCode.BAD_REQUEST_PARAMS));
    }

    private void handleHttpMessageNotReadableException(HttpMessageNotReadableException ex, HttpServletResponse response) throws IOException {
        response.setStatus(HttpStatus.BAD_REQUEST.value());
        mapper.writeValue(response.getWriter(), FeesErrorResponse.of(ex.getMessage(),
                FeesErrorCode.BAD_REQUEST_PARAMS));
    }

    private void handleFeesException(FeesException ex, HttpServletResponse response) throws IOException {
        log.debug("业务逻辑错误:{}", ex.getMessage());
        FeesErrorCode errorCode = ex.getErrorCode();
        HttpStatus status = HttpStatus.OK;
        response.setStatus(status.value());
        response.setCharacterEncoding("utf-8");
        mapper.writeValue(response.getWriter(), FeesErrorResponse.of(ex.getMessage(), errorCode));
    }

    private void handleSubscriptionException(FeesException subscriptionException, HttpServletResponse response) throws IOException {
        response.setStatus(HttpStatus.FORBIDDEN.value());
        response.setCharacterEncoding("utf-8");
        mapper.writeValue(response.getWriter(),
                (new ObjectMapper()).readValue(((HttpClientErrorException) subscriptionException.getCause()).getResponseBodyAsByteArray(), Object.class));
    }

    private void handleAccessDeniedException(HttpServletResponse response) throws IOException {
        response.setCharacterEncoding("utf-8");
        response.setStatus(HttpStatus.FORBIDDEN.value());
        mapper.writeValue(response.getWriter(),
                FeesErrorResponse.of("PERMISSION_DENIED",
                        FeesErrorCode.PERMISSION_DENIED));

    }

    private void handleAuthenticationException(AuthenticationException authenticationException, HttpServletResponse response) throws IOException {
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setCharacterEncoding("utf-8");
        if (authenticationException instanceof BadCredentialsException || authenticationException instanceof UsernameNotFoundException) {
            mapper.writeValue(response.getWriter(), FeesErrorResponse.of("USERNAME_PASSWORD_INCORRECT", FeesErrorCode.AUTHENTICATION));
        } else if (authenticationException instanceof DisabledException) {
            mapper.writeValue(response.getWriter(), FeesErrorResponse.of("ACCOUNT_IS_NOT_ACTIVE", FeesErrorCode.AUTHENTICATION));
        } else if (authenticationException instanceof LockedException) {
            mapper.writeValue(response.getWriter(), FeesErrorResponse.of("ACCOUNT_LOCKED", FeesErrorCode.AUTHENTICATION));
        } else {
            mapper.writeValue(response.getWriter(), FeesErrorResponse.of("AUTHENTICATION_METHOD_NOT_SUPPORTED", FeesErrorCode.AUTHENTICATION));
        }
    }


}
