package com.jy.framework.mybatis;

import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.jy.framework.model.constants.ProfileConstant;
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.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

import java.sql.Connection;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;

/**
 * 打印执行的sql日志
 * {@link <a href="https://blog.csdn.net/sinosoft12345/article/details/110926677" /> }
 */
@Slf4j
@Component
@Profile(ProfileConstant.DEV)
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class SqlLogInterceptor implements Interceptor {

    /**
     * 拦截器对应的封装原始对象的方法，获取代理对象
     */
    @Override
    public Object plugin(Object target) {
        return (target instanceof StatementHandler) ? Plugin.wrap(target, this) : target;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        long start = System.currentTimeMillis();
        Object result = invocation.proceed();
        long end = System.currentTimeMillis();

        // 获取 StatementHandler ，默认是 RoutingStatementHandler
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        // 获取 StatementHandler 包装类
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        // 获取查询接口映射的相关信息
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        // 绑定sql
        BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");

        // 获取sql
        String sql = getSql(mappedStatement.getConfiguration(), boundSql);

        // 打印 sql
        log.info("""
                        
                        ====================  Sql Start  ====================
                         方法： {}
                         耗时： {}
                         语句： {}
                        ====================  Sql  End   ====================
                        """,
                mappedStatement.getId(), DateUtil.formatBetween(end - start), sql);

        return result;
    }

    /**
     * 获取sql
     */
    private String getSql(Configuration configuration, BoundSql boundSql) {
        // sql语句中多个空格都用一个空格代替
        String sql = boundSql.getSql().replaceAll("\\s+", " ");

        // 获取参数
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

        if (CollectionUtils.isEmpty(parameterMappings) || parameterObject == null) {
            return sql;
        }

        // 获取类型处理器注册器，类型处理器的功能是进行java类型和数据库类型的转换
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();

        // 如果根据parameterObject.getClass(）可以找到对应的类型，则替换
        if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            return sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(parameterObject)));
        }

        // MetaObject主要是封装了originalObject对象，
        // 提供了get和set的方法用于获取和设置originalObject的属性值,
        // 主要支持对JavaBean、Collection、Map三种类型对象的操作
        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("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
            } else if (boundSql.hasAdditionalParameter(propertyName)) {
                // 该分支是动态sql
                Object obj = boundSql.getAdditionalParameter(propertyName);
                sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
            } else {
                // 打印出缺失，提醒该参数缺失并防止错位
                sql = sql.replaceFirst("\\?", "${参数缺失}");
            }
        }

        return sql;
    }

    /**
     * 获取参数值
     */
    private String getParameterValue(Object obj) {

        if (obj instanceof String string) {
            return StringPool.SINGLE_QUOTE + string + StringPool.SINGLE_QUOTE;
        }

        if (obj instanceof Date date) {
            return StringPool.SINGLE_QUOTE + DateUtil.formatDateTime(date) + StringPool.SINGLE_QUOTE;
        }

        if (obj != null) {
            return obj.toString();
        }

        return StringPool.EMPTY;
    }

}
