package com.reptileclient.jlifeapp.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Properties;

/**
 * @author fuxubing
 */
@Slf4j
@Component
@Profile({"uat"})
@Intercepts({
        /*@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),*/
        @Signature(type = StatementHandler.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = StatementHandler.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class MybatisInterceptor implements Interceptor {

    //@Value("${iwiteks.tools.plugins.enable}")
    private Boolean enable = true;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        this.beginPluginAction(invocation);
        // 执行之前处理
        Object result = invocation.proceed();
        // 执行之后处理
        return result;
    }

    @Override
    public Object plugin(Object target) {// 复写本方法时，最好参考官方文档，网上博客异常的乱，运行不出来或者运行会导致mybatis本身运行异常的太多，谨慎复写该方法
        return Proxy.newProxyInstance(Interceptor.class.getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return intercept(new Invocation(target, method, args));
            }
        });
    }

    /**
     * 主要是拿来区别数据库，比如mysql，mssql，oracle等，这点在分页插件上有很大作用
     * 但是目前本拦截器只是为了将参数塞进sql中，在日志中打印出完整sql，如果需要setProperties生效，只有在
     * mybatis的配置文件中的plugins中的plugin节点加入本拦截方法，不然本方法将无效
     *
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
        log.info("start init mybatis properties......");
    }

    /**
     * 开始执行
     *
     * @param invocation
     * @throws Throwable
     */
    private void beginPluginAction(Invocation invocation) {
        if (!enable) {
            return;
        }
        if (invocation.getTarget() instanceof StatementHandler) {
            StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
            MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
            //先拦截到RoutingStatementHandler，里面有个StatementHandler类型的delegate变量，其实现类是BaseStatementHandler，然后就到BaseStatementHandler的成员变量mappedStatement
            MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
            if (invocation.getArgs().length > 1) {
                Configuration configuration = mappedStatement.getConfiguration();
                // System.err.println(this.formatterSql(configuration, statementHandler.getBoundSql()));
                String type = mappedStatement.getSqlCommandType().toString();
                if (type.equals("SELECT")) {
                    log.error("(execute query)==> " + this.formatterSql(configuration, statementHandler.getBoundSql()));
                } else if (type.equals("INSERT") || type.equals("UPDATE")) {
                    log.error("(execute insert or update)==> " + this.formatterSql(configuration, statementHandler.getBoundSql()));
                } else {
                    log.error("(execute delete)==> " + this.formatterSql(configuration, statementHandler.getBoundSql()));
                }
            }
        }
        /*if (invocation.getTarget() instanceof Executor) {// 判断执行的接口对象
            if (invocation.getArgs()[0] instanceof MappedStatement) {// 避免类型转换异常导致mybatis查询异常
                MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
                Object parameter = null;
                if (invocation.getArgs().length > 1) {
                    parameter = invocation.getArgs()[1];
                }

                BoundSql boundSql = mappedStatement.getBoundSql(parameter);
                System.err.println(boundSql.getSql());
                Configuration configuration = mappedStatement.getConfiguration();
                String type = mappedStatement.getSqlCommandType().toString();
                if (type.equals("SELECT")) {
                    log.debug("(execute query)==> " + this.formatterSql(configuration, boundSql));
                } else if (type.equals("INSERT") || type.equals("UPDATE")) {
                    log.info("(execute insert or update)==> " + this.formatterSql(configuration, boundSql));
                } else {
                    log.warn("(execute delete)==> " + this.formatterSql(configuration, boundSql));
                }
            }
        }*/
    }

    /**
     * 获取查询参数
     *
     * @param obj
     * @return
     */
    private String getParameterValue(Object obj) {
        String value;
        if (obj instanceof String) {
            value = "'" + obj.toString() + "'";
        } else if (obj instanceof Date) {
            DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
            value = "'" + formatter.format(obj) + "'";
        } else {
            if (obj != null) {
                value = obj.toString();
            } else {
                value = "''";
            }
        }
        return value;
    }

    /**
     * 格式化sql脚本
     *
     * @param configuration
     * @param boundSql
     * @return
     */
    public String formatterSql(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        if (parameterMappings.size() > 0 && parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));
            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    }
                }
            }
        }
        return sql;
    }
}

