package com.wecloud.dispatch.general.impl;

import com.wecloud.dispatch.ActionContext;
import com.wecloud.dispatch.common.ApplyInfo;
import com.wecloud.dispatch.extend.ActionMethodInterceptor;
import com.wecloud.dispatch.extend.ActionRequest;
import com.wecloud.dispatch.extend.ArgumentBox;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.SmartFactoryBean;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ClassUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import javax.validation.executable.ExecutableValidator;
import java.lang.reflect.Method;
import java.util.Set;

/**
 * @author lixiaozhong
 */
public class GeneralActionMethodValidInterceptor implements ActionMethodInterceptor {
	Validator validator;

	public GeneralActionMethodValidInterceptor() {

		ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
		// messageSource.setBasename("classpath:i18n/ValidationMessages");
		OptionalValidatorFactoryBean optionalValidatorFactoryBean = new OptionalValidatorFactoryBean();
		optionalValidatorFactoryBean.setValidationMessageSource(messageSource);
		optionalValidatorFactoryBean.afterPropertiesSet();
		validator = optionalValidatorFactoryBean;
	}

	@Override
	public ApplyInfo intercept(ActionContext actionContext, Object object, Method method, Object[] array, MethodParameter[] parameter, ActionRequest request, ArgumentBox argumentBox) {
		// TODO Auto-generated method stub
		boolean approve = true;
		Object value = null;

		if (!isFactoryBeanMetadataMethod(method)) {
			Class<?>[] groups = determineValidationGroups(object, method);

			// Standard Bean Validation 1.1 API
			ExecutableValidator execVal = this.validator.forExecutables();
			Method methodToValidate = method;
			Set<ConstraintViolation<Object>> result;

			try {
				result = execVal.validateParameters(object, methodToValidate, array, groups);
			} catch (IllegalArgumentException ex) {
				// Probably a generic type mismatch between interface and impl as reported in
				// SPR-12237 / HV-1011
				// Let's try to find the bridged method on the implementation class...
				methodToValidate = BridgeMethodResolver.findBridgedMethod(ClassUtils.getMostSpecificMethod(method, object.getClass()));
				result = execVal.validateParameters(object, methodToValidate, array, groups);
			}
			if (!result.isEmpty()) {
				throw new ConstraintViolationException(result);
			}
			if (null != array) {
				int size = array.length;
				for (int i = 0; i < size; i++) {
					Validated validatedAnn = parameter[i].getParameterAnnotation(Validated.class);
					if (validatedAnn != null) {
						Class<?>[] gs = validatedAnn.value();
						result = validator.validate(array[i], gs);
						if (!result.isEmpty()) {
							throw new ConstraintViolationException(result);
						}
					}
				}
			}
		}
		ApplyInfo applyInfo = new ApplyInfo();
		applyInfo.setApprove(approve);
		applyInfo.setValue(value);
		return applyInfo;
	}

	private boolean isFactoryBeanMetadataMethod(Method method) {
		Class<?> clazz = method.getDeclaringClass();

		// Call from interface-based proxy handle, allowing for an efficient check?
		if (clazz.isInterface()) {
			return ((clazz == FactoryBean.class || clazz == SmartFactoryBean.class) &&
					!"getObject".equals(method.getName()));
		}

		// Call from CGLIB proxy handle, potentially implementing a FactoryBean method?
		Class<?> factoryBeanType = null;
		if (SmartFactoryBean.class.isAssignableFrom(clazz)) {
			factoryBeanType = SmartFactoryBean.class;
		} else if (FactoryBean.class.isAssignableFrom(clazz)) {
			factoryBeanType = FactoryBean.class;
		}
		return (factoryBeanType != null && !method.getName().equals("getObject") &&
				ClassUtils.hasMethod(factoryBeanType, method.getName(), method.getParameterTypes()));
	}

	/**
	 * Determine the validation groups to validate against for the given method
	 * invocation.
	 * <p>
	 * Default are the validation groups as specified in the {@link Validated}
	 * annotation on the containing target class of the method.
	 *
	 * @param method the current MethodInvocation
	 * @return the applicable validation groups as a Class array
	 */
	protected Class<?>[] determineValidationGroups(Object bean, Method method) {
		Validated validatedAnn = AnnotationUtils.findAnnotation(method, Validated.class);
		if (validatedAnn == null) {
			validatedAnn = AnnotationUtils.findAnnotation(bean.getClass(), Validated.class);
		}
		return (validatedAnn != null ? validatedAnn.value() : new Class<?>[0]);
	}
}
