package com.hzya.frame.jdbcUtil.service;

import org.apache.ibatis.executor.Executor;
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.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;

import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

/**
 * @ClassName: SqlLogInterceptor
 * @Description: TODO
 * @Author: youchao
 * @Date: 2019/10/29 18:31
 * @Version: 1.0
 */
@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})})

@Order(1)
public class SqlLogInterceptor implements Interceptor {
    Logger log = LoggerFactory.getLogger(SqlLogInterceptor.class);
    private Boolean enableSqlLogInterceptor = true;
    /**
     * 关注时间 单位秒，默认值 5
     * 如果 执行SQL 超过时间 就会打印error 日志
     */
    private Integer noticeTime = 5;
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long start = System.currentTimeMillis();

        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        String sqlName = mappedStatement.getId();
        Object parameter = null;
        if (invocation.getArgs().length > 1) {
            parameter = invocation.getArgs()[1];
        }
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        Configuration configuration = mappedStatement.getConfiguration();
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        List<String> paramList = getParamList(configuration, boundSql);
        if(sql.split("\\?").length > 1 && paramList.size() > 0){
            StringBuffer stringBuffer = new StringBuffer();
            String[] sqls = sql.split("\\?");
            for (int i = 0; i < sqls.length; i++) {
                stringBuffer.append(sqls[i]);
                if(i < paramList.size()){
                    stringBuffer.append(paramList.get(i));
                }
            }
             log.info("completeSql："+stringBuffer.toString());
        }else {
            log.info("completeSql："+sql);
        }
        Object proceed = invocation.proceed();
        int result = 0;
        if (proceed instanceof ArrayList) {
            ArrayList resultList = (ArrayList) proceed;
            result = resultList.size();
        }
        if (proceed instanceof Integer) {
            result = (Integer) proceed;
        }
        if (enableSqlLogInterceptor) {
            long end = System.currentTimeMillis();
            long time = end - start;
            if (time >= noticeTime * 1000) {
                log.info("执行超过"+noticeTime+"秒,sql="+sql );
                log.info("result={}, 执行时间time={}ms, params={} ,sqlNamw={}", result, time, paramList,sqlName);
                return proceed;
            }else {
                log.info("result={}, 执行时间time={}ms, params={} ,sqlNamw={}", result, time, paramList,sqlName);
            }
        }
        return proceed;
    }

    /**
     * Instantiates a new Sql log interceptor.
     */
    public SqlLogInterceptor() {
        //log.info("[打印SQL拦截器创建]noticeTime={}秒", noticeTime);
    }

    /**
     * 设置执行sql
     *
     * @param noticeTime 关注时间 ，如果执行sql超过关注时间，打印error日志.
     */
    public SqlLogInterceptor(Integer noticeTime) {
        this.noticeTime = noticeTime;
        //log.info("[打印SQL拦截器创建]noticeTime={}秒", noticeTime);
    }

    /**
     * Plugin object.
     *
     * @param target the target
     *
     * @return the object
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    /**
     * 获取sql参数集合。
     *
     * @param configuration the configuration
     * @param boundSql      the bound sql
     *
     * @return the param list
     */
    private List<String> getParamList(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        List<String> params = new ArrayList<>();
        if (parameterMappings.size() > 0 && parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                params.add(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);
                        params.add(getParameterValue(obj));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        params.add(getParameterValue(obj));
                    }
                }
            }
        }
        return params;
    }

    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;
    }
}
