package ${vhGrpcBuilder_packageName}.${vhGrpcBuilder_sprojectName}.util;

import com.viewhigh.vhsc.support.domain.ApiResult;
import com.viewhigh.vhsc.support.error.CheckedException;
import com.viewhigh.vhsc.support.error.ErrorInfo;
import com.viewhigh.vhsc.support.util.ObjectUtils;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
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.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Locale;
import java.util.Set;


/**
 * 异常处理
 * @author gexiangping
 *
 */
@RestControllerAdvice
public class ApiExceptionHandler {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(ApiExceptionHandler.class);

	@Autowired
	private MessageSource messageSource;

	private String extraceMessage(BindingResult bindingResult) {
		// 抽取错误
		StringBuilder sb = new StringBuilder();
		List<ObjectError> globalErrors = bindingResult.getGlobalErrors();
		for (ObjectError objectError : globalErrors) {
			sb.append(messageSource.getMessage(objectError, Locale.CHINA));
			sb.append(StringUtils.SPACE);
		}
		List<FieldError> fieldErrors = bindingResult.getFieldErrors();
		for (FieldError fieldError : fieldErrors) {
			String msg = messageSource.getMessage(fieldError, Locale.CHINA);
			sb.append(fieldError.getField()).append(msg);
			sb.append(StringUtils.SPACE);
		}
		String message = sb.toString();
		LOGGER.warn(message);
		return message;
	}

	/**
	 * 数据绑定-BindException
	 * 
	 * @param ex
	 * @return
	 */
	@ExceptionHandler(value = { BindException.class })
	@ResponseBody
	public ApiResult<String> handleException(BindException ex) {
		String message = extraceMessage(ex);
		return ApiResult.errorResult(ErrorInfo.PARAM_INVALID, message);
	}

	/**
	 * 数据绑定-MethodArgumentNotValidException
	 * 
	 * @param ex
	 * @return
	 */
	@ExceptionHandler(value = { MethodArgumentNotValidException.class })
	@ResponseBody
	public ApiResult<String> handleException(MethodArgumentNotValidException ex) {
		String message = extraceMessage(ex.getBindingResult());
		return ApiResult.errorResult(ErrorInfo.PARAM_INVALID, message);
	}

	/**
	 * 数据绑定-ServletRequestBindingException
	 * 
	 * @param ex
	 * @return
	 */
	@ExceptionHandler(value = { ServletRequestBindingException.class })
	@ResponseBody
	public ApiResult<String> handleException(ServletRequestBindingException ex) {
		LOGGER.warn(ex.getMessage());
		return ApiResult.errorResult(ErrorInfo.PARAM_INVALID);
	}

	/**
	 * 数据绑定-TypeMismatchException
	 * 
	 * @param ex
	 * @return
	 */
	@ExceptionHandler(value = { TypeMismatchException.class })
	@ResponseBody
	public ApiResult<String> handleException(TypeMismatchException ex) {
		String code = ex.getErrorCode() + "." + ex.getRequiredType().getName();
		String message = ex.getValue() + messageSource.getMessage(code, new Object[] {}, Locale.CHINA);
		LOGGER.warn("{}, {}", message, ex.getMessage());
		return ApiResult.errorResult(ErrorInfo.PARAM_INVALID, message);
	}

	/**
	 * 验证异常
	 * 
	 * @param ex
	 * @return
	 */
	@ExceptionHandler(value = { ConstraintViolationException.class })
	@ResponseBody
	public ApiResult<String> handleException(ConstraintViolationException ex) {
		Set<ConstraintViolation<?>> volations = ex.getConstraintViolations();
		for (ConstraintViolation<?> violation : volations) {
			return ApiResult.errorResult(ErrorInfo.PARAM_INVALID, violation.getMessage());
		}
		LOGGER.warn(ex.getMessage());
		return ApiResult.errorResult(ErrorInfo.PARAM_INVALID);
	}

	/**
	 * CheckedException
	 * 
	 * @param ex
	 * @return
	 */
	@ExceptionHandler(CheckedException.class)
	@ResponseBody
	public ApiResult<Void> handleCheckedException(CheckedException ex) {
		return ApiResult.errorResult(ex.getErrorCode(), ex.getErrorMessage());
	}

	/**
	 * 内部服务错误(包含GRPC服务中的CheckedException)
	 * 
	 * @param ex
	 * @return
	 */
	@ExceptionHandler({ StatusRuntimeException.class })
	@ResponseBody
	public ApiResult<Void> handleGrpcStatusRuntimeException(StatusRuntimeException ex) {
		Status status = ex.getStatus();
		if (status.getCode() == Status.Code.INTERNAL) {
			String desc = status.getDescription();
			if (ObjectUtils.isNotEmpty(desc) && desc.contains("|")) {
				String[] error = desc.split("\\|");
				if (error.length >= 2) {
					LOGGER.info("checked exception, error_code:{},error_msg:{}",error[0], error[1]);
					return ApiResult.errorResult(error[0], error[1]);
				}
			}
		}
		LOGGER.error(ex.getMessage(), ex);
		return ApiResult.errorResult(ErrorInfo.SYS_INTERNAL_SVC_ERROR);
	}

	/**
	 * 服务端错误,未捕获异常
	 * 
	 * @param ex
	 * @return
	 */
	@ExceptionHandler({ RuntimeException.class, Exception.class })
	@ResponseBody
	@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
	public ApiResult<Void> handleUnexpectedServerError(Exception ex) {
		LOGGER.error(ex.getMessage(), ex);
		return ApiResult.errorResult(ErrorInfo.SYS_SERVER_ERROR);
	}
    
}