package cn.axzo.msg.center.notices.client.config;

import cn.axzo.trade.datasecurity.mybatisplus.interceptor.MybatisPlusCryptInterceptor;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.InterceptorChain;
import org.apache.ibatis.session.SqlSessionFactory;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Set;

/**
 * @author yanglin
 */
@Slf4j
//@Component
public class MybatisInterceptorAnalyzeProcessor implements BeanPostProcessor {

    private static final Set<Class<?>> EXPECTED_INTERCEPTORS = Sets.newHashSet(
            MybatisPlusCryptInterceptor.class);

    @Override
    public Object postProcessBeforeInitialization(
            @NotNull Object bean, @NotNull String beanName) throws BeansException {
        if (bean instanceof SqlSessionFactory) {
            try {
                analyze((SqlSessionFactory) bean);
            } catch (Exception e) {
                log.warn("SqlSessionFactory analyze error", e);
            }
        }
        return bean;
    }

    private void analyze(SqlSessionFactory sessionFactory) {
        org.apache.ibatis.session.Configuration configuration = sessionFactory.getConfiguration();
        InterceptorChain oldChain = getFieldValue(configuration, "interceptorChain");
        List<Interceptor> oldInterceptors = getFieldValue(oldChain, "interceptors");
        AnalyzeInterceptorChain newChain = new AnalyzeInterceptorChain();
        for (Interceptor interceptor : oldInterceptors)
            newChain.addInterceptor(interceptor);
        setFieldValue(configuration, "interceptorChain", newChain);
    }

    @SuppressWarnings({"unchecked", "DataFlowIssue"})
    private static <T> T getFieldValue(Object obj, String fieldName) {
        Field field = ReflectionUtils.findField(obj.getClass(), fieldName);
        field.setAccessible(true);
        return (T)ReflectionUtils.getField(field, obj);
    }

    @SuppressWarnings("DataFlowIssue")
    private static void setFieldValue(Object obj, String fieldName, Object value) {
        Field field = ReflectionUtils.findField(obj.getClass(), fieldName);
        Field modifiers = ReflectionUtils.findField(Field.class, "modifiers");
        field.setAccessible(true);
        modifiers.setAccessible(true);
        ReflectionUtils.setField(modifiers, field, field.getModifiers() & ~Modifier.FINAL);
        ReflectionUtils.setField(field, obj, value);
    }

    private static class AnalyzeInterceptorChain extends InterceptorChain {
        @Override
        public void addInterceptor(Interceptor interceptor) {
            super.addInterceptor(interceptor);
            if (EXPECTED_INTERCEPTORS.contains(interceptor.getClass()))
                log.info("Adding interceptor={}", interceptor.getClass().getName());
            else
                log.warn("Unexpected interceptor={}", interceptor.getClass().getName(), new RuntimeException());
        }
    }

}