package com.hotent.base.handler;

import java.io.Serializable;
import java.net.ConnectException;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;

import com.hotent.base.exception.MessagePassingException;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.ibatis.binding.BindingException;
import org.apache.logging.log4j.util.Strings;
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.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
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.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

import com.fasterxml.jackson.databind.node.ObjectNode;
import com.hotent.base.aop.AopCacheHelper;
import com.hotent.base.constants.JmsConstant;
import com.hotent.base.constants.TenantConstant;
import com.hotent.base.enums.ResponseErrorEnums;
import com.hotent.base.exception.BaseException;
import com.hotent.base.jms.JmsProducer;
import com.hotent.base.model.CommonResult;
import com.hotent.base.util.AppUtil;
import com.hotent.base.util.AuthenticationUtil;
import com.hotent.base.util.BeanUtils;
import com.hotent.base.util.ExceptionUtil;
import com.hotent.base.util.HttpUtil;
import com.hotent.base.util.JsonUtil;
import com.hotent.base.util.StringUtil;
import com.hotent.base.util.ThreadMsgUtil;
import com.hotent.base.util.UniqueIdUtil;
import com.hotent.base.util.WebUtil;
import com.hotent.base.util.time.DateUtil;
import com.netflix.hystrix.exception.HystrixRuntimeException;

import feign.RetryableException;

/**
 * 全局的异常处理类
 *
 * @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";
    /**
     * Mysql重复键值异常信息匹配规则
     */
    private static Pattern regexDuplicateKeyMysql = Pattern.compile("\\'[^']+\\'");
    /**
     * Mysql重复键值异常信息匹配规则
     */
    private static Pattern regexDuplicateKeyOracle = Pattern.compile("\\([^)]+\\)");
    
    /**
     * 忽略的不向前端展示的异常信息
     */
    final static List<String> IGNORE_EXCEPTION_MESSAGE = Arrays.asList("org.activiti.engine.ActivitiException:", "Exception while invoking TaskListener:",
    		"com.hotent.base.exception.","任务办理失败:","BaseException:");


    @Value("${spring.application.name: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 {}", e);
        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 {}", e);
        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 {}", e);
        saveExceptionLogs(opeContent);
        return new CommonResult<>(ResponseErrorEnums.BOUND_STATEMENT_NOT_FOUNT);
    }
    
    /**
     * 异步任务队列超过最大承载数时会拒绝新的请求任务
     *
     * @param e
     * @return
     */
    @ExceptionHandler(RejectedExecutionException.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public CommonResult<String> rejectedExecution(Exception e) {
        String opeContent = ExceptionUtil.getFullStackTrace(e);
        log.error("occurs error when execute method ,message {}", e);
        return new CommonResult<>(ResponseErrorEnums.REJECTED_EXECUTION_ERROR);
    }
    
    
    
	/**
	 * 自定义异常的捕获 自定义抛出异常。统一的在这里捕获返回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, exception);
		log.error(ExceptionUtil.getFullStackTrace(exception));
        String errorId = saveExceptionLogs(opContent);
        return CommonResult.<T>error().log(errorId).message(exception.getMessage());
	}

	/**
	 * 数据库操作出现异常
	 * 
	 * @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 {}", e);
		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 {}", e);
        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 {}",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 {}", e);
        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, formatMsg(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 {}", e);
        String errorId = saveExceptionLogs(opeContent);
        CommonResult<String> commonResult = new CommonResult<>(false,formatMsg(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 CommonResult.<String>result(ResponseErrorEnums.ILLEGAL_ARGUMENT).message(stringBuffer.toString());
    }

    /**
     *
     */
    @ExceptionHandler(value = {MessagePassingException.class})
    @ResponseBody
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public CommonResult<String> dataOutOrLimitExceptionHandler(MessagePassingException e) {
        log.error("参数不合法{}",e);
        return CommonResult.<String>result(ResponseErrorEnums.ILLEGAL_ARGUMENT).message(e.getMessage());
    }

    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_LOG_QUEUE);
			}
		} catch (Exception err) {
			log.error("保存异常日志失败。" + ExceptionUtils.getFullStackTrace(err));
		}
		return errorId;
	}
    
    public static String formatMsg(String msg){
    	if (StringUtil.isEmpty(msg)) {
    		return msg;
		}
    	for (String message : IGNORE_EXCEPTION_MESSAGE) {
    		msg = msg.replaceAll(message, "");
		}
		return msg.trim();
    }

    /**
     * 数据库唯一索引列数据重复异常处理
     *
     * @param e
     * @return
     */
    @ExceptionHandler(DuplicateKeyException.class)
    @ResponseBody
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public CommonResult<String> duplicateKeyExceptionHandler(Exception e) {
        String opeContent = ExceptionUtil.getFullStackTrace(e);
        log.error("[ExceptionHandler]occurs error when execute method ,message {}", e);
        String errorId = saveExceptionLogs(opeContent);
        String errorMsg = Strings.EMPTY;
        Matcher regexMatcher = regexDuplicateKeyMysql.matcher(e.getCause().getMessage());
        if(!regexMatcher.find()){
            regexMatcher = regexDuplicateKeyOracle.matcher(e.getCause().getMessage());
        }
        while (regexMatcher.find()) {
            String msg = regexMatcher.group(0);
            if(msg.contains(".")){
                String[] arr = msg.split("\\.");
                String indexName = arr[1];
                if(StringUtil.isNotEmpty(indexName)){
                    indexName = indexName.replace("'", "").replace(")", "");
                    String concatChar = StringUtil.isEmpty(errorMsg)?"": ",";
                    errorMsg += concatChar + indexName;
                }
            }
        }
        if(StringUtil.isEmpty(errorMsg)){
            errorMsg = "您填写的内容有重复，请核查！";
        }
        CommonResult<String> commonResult = new CommonResult<>(false, errorMsg);
        commonResult.setLogId(errorId);
        return commonResult;
    }
}
