package com.artfess.base.handler;

import com.artfess.base.aop.AopCacheHelper;
import com.artfess.base.constants.JmsConstant;
import com.artfess.base.constants.TenantConstant;
import com.artfess.base.enums.ResponseErrorEnums;
import com.artfess.base.exception.BaseException;
import com.artfess.base.jms.JmsProducer;
import com.artfess.base.model.CommonResult;
import com.artfess.base.util.*;
import com.artfess.base.util.time.DateUtil;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.netflix.hystrix.exception.HystrixRuntimeException;
import feign.RetryableException;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.ibatis.binding.BindingException;
import org.mybatis.spring.MyBatisSystemException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.net.ConnectException;
import java.sql.SQLException;
import java.util.concurrent.TimeoutException;

/**
 * 全局的异常处理类
 *
 * @company 广州宏天软件股份有限公司
 * @author heyifan
 * @email heyf@jee-soft.cn
 * @date 2020年4月4日
 */
@RestControllerAdvice(annotations = { RestController.class, Controller.class })
public class BaseExceptionHandler {

    private final Logger log = LoggerFactory.getLogger(getClass());

    private static String moduleType = "base" ;

    @Value("${spring.profiles.title:base}")
    public void setModuleType(String param){
        moduleType = param;
    }

    /**
     * 请求参数类型错误异常的捕获
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = { BindException.class })
    @ResponseBody
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public CommonResult<String> badRequest(BindException e) {
        String opeContent = ExceptionUtil.getFullStackTrace(e);
        log.error("occurs error when execute method ,message {}", opeContent);
        saveExceptionLogs(opeContent);
        return new CommonResult<>(ResponseErrorEnums.BAD_REQUEST);
    }

    /**
     * 404错误异常的捕获
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = { NoHandlerFoundException.class })
    @ResponseBody
    @ResponseStatus(value = HttpStatus.NOT_FOUND)
    public CommonResult<String> badRequestNotFound(BindException e) {
        String opeContent = ExceptionUtil.getFullStackTrace(e);
        log.error("occurs error when execute method ,message {}", opeContent);
        saveExceptionLogs(opeContent);
        return new CommonResult<>(ResponseErrorEnums.NOT_FOUND);
    }

    /**
     * mybatis未绑定异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(BindingException.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public CommonResult<String> mybatis(Exception e) {
        String opeContent = ExceptionUtil.getFullStackTrace(e);
        log.error("occurs error when execute method ,message {}", opeContent);
        saveExceptionLogs(opeContent);
        return new CommonResult<>(ResponseErrorEnums.BOUND_STATEMENT_NOT_FOUNT);
    }
    
	/**
	 * 自定义异常的捕获 自定义抛出异常。统一的在这里捕获返回JSON格式的友好提示。
	 * 
	 * @param exception
	 * @param request
	 * @return
	 */
	@ExceptionHandler(value = { BaseException.class })
	@ResponseBody
	@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
	public <T extends Serializable> CommonResult<T> sendError(BaseException exception, HttpServletRequest request) {
		String requestURI = request.getRequestURI();
		String opContent = getErrorDetail(exception);
		log.error("occurs error when execute url ={} ,message {}", requestURI, opContent);
		String errorId = saveExceptionLogs(opContent);
        CommonResult<T> commonResult = new CommonResult<>(exception.getCode(), exception.getMessage());
        commonResult.setLogId(errorId);
        return commonResult;
	}

	/**
	 * 数据库操作出现异常
	 * 
	 * @param e
	 * @return
	 */
	@ExceptionHandler(value = { SQLException.class, DataAccessException.class })
	@ResponseBody
	@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
	public CommonResult<String> systemError(Exception e) {
        String opeContent = ExceptionUtil.getFullStackTrace(e);
        log.error("occurs error when execute method ,message {}", opeContent);
		String errorId = saveExceptionLogs(opeContent);
		if(e instanceof MyBatisSystemException){
		    return new CommonResult<>(((MyBatisSystemException) e).getRootCause().getMessage());
        }else{
        	CommonResult<String> commonResult = new CommonResult<>(ResponseErrorEnums.DATABASE_ERROR);
        	commonResult.setMessage( "数据库操作失败，请联系管理员或查看日志！");
        	commonResult.setLogId(errorId);
            return commonResult;
        }
    }

    /**
     * 网络连接失败！
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = { ConnectException.class })
    @ResponseBody
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public CommonResult<String> connect(Exception e) {
        String opeContent = ExceptionUtil.getFullStackTrace(e);
        log.error("occurs error when execute method ,message {}", opeContent);
        saveExceptionLogs(opeContent);
        return new CommonResult<>(ResponseErrorEnums.CONNECTION_ERROR);
	}

    @ExceptionHandler(value = { HystrixRuntimeException.class })
	@ResponseBody
	@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
	public CommonResult<String> hystrixRuntimeError(HystrixRuntimeException e) {
		log.error("occurs error when execute method ,message {}", ExceptionUtils.getFullStackTrace(e));
		CommonResult<String> result = null;
		if (e.getCause() instanceof TimeoutException || e.getCause() instanceof RetryableException) {
			result = new CommonResult<>(ResponseErrorEnums.SERVICE_INVOKE_ERROR, "服务调用超时：" + e.getLocalizedMessage());
		} else {
			result = new CommonResult<>(false, "服务调用失败：" + ExceptionUtils.getRootCauseMessage(e));
		}
		String opeContent = ExceptionUtil.getFullStackTrace(e);
		saveExceptionLogs(opeContent);
		return result;
	}

    private String getErrorDetail(BaseException e) {
    	String opeContent = "";
    	if (BeanUtils.isNotEmpty(ExceptionUtil.getRootCauseMessage(e))){
            opeContent = ExceptionUtil.getFullStackTrace(ExceptionUtil.getRootCause(e));
        }else{
            opeContent = ExceptionUtil.getFullStackTrace(e);
        }
    	return opeContent;
    }

    @ExceptionHandler(value = { RuntimeException.class })
    @ResponseBody
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public CommonResult<String> runTimeError(Exception e) {
        String opeContent = ExceptionUtil.getFullStackTrace(e);
        log.error("occurs error when execute method ,message {}", opeContent);
        String errorMsg = ExceptionUtil.getRootCauseMessage(e);
        String flowErrorMsg = ThreadMsgUtil.getMapMsg(ThreadMsgUtil.MSG_FLOW_ERROR, true);
        if (StringUtil.isNotEmpty(errorMsg) && errorMsg.indexOf("流程异常") > -1 && StringUtil.isNotEmpty(flowErrorMsg)) {
            errorMsg = flowErrorMsg;
        }else if (StringUtil.isNotEmpty(errorMsg)) {
        	String[] msgs = errorMsg.split("Exception:");
        	if(msgs.length==2){
        		errorMsg = msgs[1];
        	}
        }
        String errorId = saveExceptionLogs(opeContent);
        CommonResult<String> commonResult = new CommonResult<>(false, errorMsg);
        commonResult.setLogId(errorId);
        return commonResult;
    }

    @ExceptionHandler(value = { Exception.class })
    @ResponseBody
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public CommonResult<String> notAllowed(Exception e) {
        String opeContent = ExceptionUtil.getFullStackTrace(e);
        String errorMsg = "";
        if(BeanUtils.isNotEmpty(ExceptionUtil.extractMessageFromXML(e.getMessage()))){
        	errorMsg = ExceptionUtil.extractMessageFromXML(e.getMessage());
        }else{
        	errorMsg = e.getMessage();
        }
        log.error("occurs error when execute method ,message {}", opeContent);
        String errorId = saveExceptionLogs(opeContent);
        CommonResult<String> commonResult = new CommonResult<>(false,errorMsg);
        commonResult.setLogId(errorId);
        return commonResult;
    }

    /**
     * 校验错误信息收集
     * @param e
     * @return
     */
    @ExceptionHandler(value = { MethodArgumentNotValidException.class })
    @ResponseBody
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public CommonResult<String> methodArgumentNotValidException(MethodArgumentNotValidException e) {
        StringBuffer stringBuffer = new StringBuffer();
        BindingResult bindingResult = e.getBindingResult();
        bindingResult.getAllErrors().forEach(error-> stringBuffer.append(error.getDefaultMessage()+" "));
        log.error("occurs error when execute method ,message {}", stringBuffer.toString());
        return new CommonResult<String>(ResponseErrorEnums.ILLEGAL_ARGUMENT.getCode(),stringBuffer.toString());
    }

    /**
     * excel文件导出
     * @param e
     * @return
     */
    @ExceptionHandler(value = { HttpMessageNotWritableException.class })
    @ResponseBody
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public CommonResult<String> methodHttpMessageNotWritableException(HttpMessageNotWritableException e) {
        StringBuffer stringBuffer = new StringBuffer();
        //处理MySqlInterceptor下载文件application/octet-stream异常
        if(null!=e.getMessage()) {
            if (e.getMessage().indexOf("application/octet-stream") != -1) {
                //e.printStackTrace();
                return null;
            }
        }
        return new CommonResult<String>(ResponseErrorEnums.ILLEGAL_ARGUMENT.getCode(),stringBuffer.toString());
    }

    private String addErrorIdForMsg(String errorId, String errorMsg) {
    	return "【日志ID："+errorId+"】" + errorMsg;
    }

    private String saveExceptionLogs(String opeContent) {
		AopCacheHelper aopCacheHelper = AppUtil.getBean(AopCacheHelper.class);
		JmsProducer jmsProducer = AppUtil.getBean(JmsProducer.class);
		String errorId = "";
		try {
		    /*
			Map<String, String> cacheSettings = aopCacheHelper.getSysLogsSettingStatusMap();
			if(BeanUtils.isEmpty(cacheSettings) || !cacheSettings.containsKey(moduleType)) {
				log.error("未获取到日志配置中关于模块：{}的日志配置，跳过该模块的日志记录。", moduleType);
			}
			*/
			
			String executor = "系统[无用户登录系统]";
			if(StringUtil.isNotEmpty(AuthenticationUtil.getCurrentUserFullname())){
				executor = String.format("%s[%s]",AuthenticationUtil.getCurrentUserFullname(),AuthenticationUtil.getCurrentUsername());  
			}
			HttpServletRequest request = HttpUtil.getRequest();
			String reqUrl = HttpUtil.getRequest().getRequestURI();
			ObjectNode objectNode = JsonUtil.getMapper().createObjectNode();
			errorId = UniqueIdUtil.getSuid();
			objectNode.put("id", errorId);
			objectNode.put("opeName", "系统异常");
			objectNode.put("moduleType", moduleType);
			objectNode.put("reqUrl", reqUrl);
			objectNode.put("opeContent", opeContent);
			objectNode.put("type", "sysLog");
			objectNode.putPOJO("executionTime", DateUtil.getCurrentDate());
			String tenantId = HttpUtil.getTenantId();
			if(BeanUtils.isEmpty(tenantId)) {
				tenantId = TenantConstant.PLATFORM_TENANT_ID;
			}
			objectNode.put("tenantId", tenantId);
			objectNode.put("logType", "异常日志");
			/*
			// 是否开始配置日志
			if(BeanUtils.isNotEmpty(cacheSettings) &&
			   cacheSettings.containsKey(moduleType) &&
			   "1".equals(cacheSettings.get(moduleType))){
				
				objectNode.put("executor", executor);
				objectNode.put("ip", WebUtil.getIpAddr(request));
				//发送错误日志信息
				jmsProducer.sendToQueue(JsonUtil.toJson(objectNode), JmsConstant.SYS_ERR_LOG_QUEUE);
			}
			 */
            objectNode.put("executor", executor);
            objectNode.put("ip", WebUtil.getIpAddr(request));
            //发送错误日志信息
            jmsProducer.sendToQueue(JsonUtil.toJson(objectNode), JmsConstant.SYS_ERR_LOG_QUEUE);
		} catch (Exception err) {
			log.error("保存异常日志失败。" + ExceptionUtils.getFullStackTrace(err));
		}
		return errorId;
	}
}
