package com.izouma.nineth.exception;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Path;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(value = BusinessException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public Map<String, Object> serviceExceptionHandler(BusinessException e) {
        Map<String, Object> map = new HashMap<>();
        map.put("error", e.getMessage());
        map.put("code", -1);
        return map;
    }

    @ExceptionHandler({AuthenticationException.class})
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ResponseBody
    public Map<String, Object> handleAuthenticationException(AuthenticationException e) {
        Map<String, Object> map = new HashMap<>();
        map.put("error", e.getMessage());
        map.put("code", -1);
        return map;
    }

    @ExceptionHandler(value = TransactionSystemException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public Map<String, Object> serviceExceptionHandler(TransactionSystemException e) {
        String message = e.getMessage();
        try {
            if (e.getCause().getCause() instanceof ConstraintViolationException) {
                ConstraintViolationException violationException = (ConstraintViolationException) e.getCause()
                        .getCause();
                message = violationException.getConstraintViolations().stream()
                        .map(constraintViolation -> constraintViolation.getPropertyPath() + constraintViolation.getMessage())
                        .collect(Collectors.joining(","));
                log.error(message);
            }
        } catch (Exception ignore) {
        }
        Map<String, Object> map = new HashMap<>();
        map.put("error", message);
        map.put("code", -1);
        return map;
    }

    @ExceptionHandler(value = Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public Object serviceExceptionHandler(Exception e, HttpServletRequest request) {
        log.error(e.getMessage(), e);
        if (request.getHeader("Accept").contains("text/html")) {
            ModelAndView modelAndView = new ModelAndView("commons/500");
            StringWriter out = new StringWriter();
            PrintWriter writer = new PrintWriter(out);
            e.printStackTrace(writer);
            String trace = out.toString();
            trace = trace.replaceAll("\n", "<br>");
            modelAndView.addObject("trace", trace);
            return modelAndView;
        } else {
            Map<String, Object> map = new HashMap<>();
            map.put("error", e.getMessage());
            map.put("code", -1);
            return map;
        }
    }

    @ExceptionHandler({BindException.class, ConstraintViolationException.class, MethodArgumentNotValidException.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public Map<String, Object> validateExceptionHandler(Exception e, HttpServletRequest request) {
        log.error("请求：{}发生异常：{}", request.getRequestURI(), e);

        // 错误信息
        StringBuilder sb = new StringBuilder("参数校验失败：");
        // 错误信息map
        Map<String, String> error = new HashMap<>();

        String msg = "";
        if (!(e instanceof BindException) && !(e instanceof MethodArgumentNotValidException)) {
            for (ConstraintViolation cv : ((ConstraintViolationException) e).getConstraintViolations()) {
                msg = cv.getMessage();
                sb.append(msg).append("；");

                Iterator<Path.Node> it = cv.getPropertyPath().iterator();
                Path.Node last = null;
                while (it.hasNext()) {
                    last = (Path.Node) it.next();
                }
                /*for(last = null; it.hasNext(); last = (Path.Node)it.next()) {
                }*/
                error.put(last != null ? last.getName() : "", msg);
            }
        } else {
            List<ObjectError> allErrors = null;
            if (e instanceof BindException) {
                allErrors = ((BindException) e).getAllErrors();
            } else {
                allErrors = ((MethodArgumentNotValidException) e).getBindingResult().getAllErrors();
            }
            // 拼接错误信息
            for (ObjectError oe : allErrors) {
                msg = oe.getDefaultMessage();
                sb.append(msg).append("；");
                if (oe instanceof FieldError) {
                    error.put(((FieldError) oe).getField(), msg);
                } else {
                    error.put(oe.getObjectName(), msg);
                }
            }
        }

        Map<String, Object> map = new HashMap<>();
        map.put("error", "参数校验失败");
        map.put("message", sb);
        map.put("code", -1);
        log.error(e.getMessage());
        return map;
    }
}
