package cn.pluss.platform.config;

import cn.pluss.platform.api.Result;
import cn.pluss.platform.api.ResultCode;
import cn.pluss.platform.api.ResultGenerator;
import cn.pluss.platform.exception.ForbiddenException;
import cn.pluss.platform.exception.MsgException;
import cn.pluss.platform.exception.ThirdException;
import cn.pluss.platform.exception.UnauthorizedException;
import cn.pluss.platform.service.UnknownExceptionService;
import cn.pluss.platform.util.ParamsCheckUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
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 javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * 全局的的异常拦截器（拦截所有的控制器）（带有@RequestMapping注解的方法上都会拦截）
 *
 * @author Djh
 */
@ControllerAdvice
@Order(-1)
@Slf4j
public class GlobalExceptionHandler {

    private static final String EXCEPTION_TIP = "异常信息: {}";

    @Resource
    private UnknownExceptionService unknownExceptionService;

    /**
     * 异常信息要传给前端的异常
     */
    @ExceptionHandler(SQLException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public Result<Object> business(SQLException e) {
        StringWriter sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw, true));
        log.error(EXCEPTION_TIP, sw);
        return ResultGenerator.genFailResult("参数包含非法字符");
    }


    /**
     * 异常信息要传给前端的异常
     */
    @ExceptionHandler(UnauthorizedException.class)
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    @ResponseBody
    public Result<Object> business(UnauthorizedException e) {
        return new Result<>(ResultCode.SERVICE_UNAVAILABLE, e.getMessage());
    }

    /**
     * 异常信息要传给前端的异常
     */
    @ExceptionHandler(MsgException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public Result<Object> business(MsgException e) {
        StringWriter sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw, true));
        log.error(EXCEPTION_TIP, sw);
        return new Result<>(e.getCode(), e.getMessage());
    }

    /**
     * 异常信息要传给前端的异常
     */
    @ExceptionHandler(ThirdException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public Result<Object> business(ThirdException e) {
        StringWriter sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw, true));
        log.error(EXCEPTION_TIP, sw);
        return new Result<>(ResultCode.FAIL, e.getMessage());
    }

    /**
     * 异常信息要传给前端的异常
     */
    @ExceptionHandler(ForbiddenException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public Result<Object> business(ForbiddenException e) {
        StringWriter sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw, true));
        log.error(EXCEPTION_TIP, sw);
        return new Result<>(ResultCode.FORBIDDEN, e.getMessage());
    }

    /**
     * 异常信息要传给前端的异常
     */
    @ExceptionHandler(DuplicateKeyException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public Result<Object> business(DuplicateKeyException e) {
        StringWriter sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw, true));
        log.error(EXCEPTION_TIP, sw);
        return ResultGenerator.genFailResult("插入失败", null);
    }


    /**
     * 参数校验错误异常
     */
    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public Result<Object> errorParams(BindException e) {
        BindingResult bindingResult = e.getBindingResult();
        log.error("参数校验异常：", e);
        return new Result<>(ResultCode.PARAM_ERROR, ParamsCheckUtil.getErrorMsg(bindingResult));
    }

    /**
     * 参数校验错误异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public Result<Object> errorParams2(MethodArgumentNotValidException e) {
        BindingResult bindingResult = e.getBindingResult();
        log.error("参数校验异常：", e);
        return new Result<>(ResultCode.PARAM_ERROR, ParamsCheckUtil.getErrorMsg(bindingResult));
    }

    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public Result<Object> unusedException() {
        return ResultGenerator.genFailResult("不支持的请求方式", null);
    }

    /**
     * 拦截未知的运行时异常
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public Result<Object> commonError(HttpServletRequest request, Exception e) {
        log.error("通用异常 URI: {}", request.getRequestURI());
        log.error("通用异常 headers: {}", JSON.toJSONString(getHeaderInfo(request)));
        try {
            InputStream is = request.getInputStream();
            StringBuilder responseStrBuilder = new StringBuilder();
            BufferedReader streamReader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
            String inputStr;
            while ((inputStr = streamReader.readLine()) != null) {
                responseStrBuilder.append(inputStr);
            }
            log.error("通用异常 body: {}", responseStrBuilder);
        } catch (Exception ignored) {
            // 不处理
        }
        Map<String, Object> parameterInfo = getParameterInfo(request);
        log.error("通用异常 params: {}",  JSON.toJSONString(parameterInfo));
        // 将异常信息保存到数据库中
        try {
            e.printStackTrace();
            unknownExceptionService.save(request, e);
        } catch (Exception ignored) {
            ignored.printStackTrace();
        }
        return new Result<>(ResultCode.PARAM_ERROR, "系统繁忙，请稍后再试");
    }

    /**
     * 拦截 @RequestParam 必传参数不存在的异常
     * @param e 异常信息
     * @return  r
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public Result<Object> missParam(MissingServletRequestParameterException e) {
        StringWriter sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw, true));
        log.error(EXCEPTION_TIP, sw);
        return new Result<>(ResultCode.PARAM_ERROR, "参数错误，或缺少必要参数");
    }

    private Map<String, Object> getParameterInfo(HttpServletRequest request) {
        Map<String, Object> parameter = new HashMap<>(8);
        Map<String, String[]> parameterMap = request.getParameterMap();

        for (Map.Entry<String, String[]> entry: parameterMap.entrySet()) {
            String key = entry.getKey();
            String[] parameterValues = entry.getValue();
            if (parameterValues.length == 1) {
                parameter.put(key, parameterValues[0]);
            } else {
                parameter.put(key, Arrays.toString(parameterValues));
            }
        }
        return parameter;
    }

    private Map<String, Object> getHeaderInfo(HttpServletRequest request) {
        Map<String, Object> parameter = new HashMap<>(8);
        Enumeration<String> headerNames = request.getHeaderNames();

        while (headerNames.hasMoreElements()) {
            String key = headerNames.nextElement();
            parameter.put(key, request.getHeader(key));
        }

        return parameter;
    }
}
