package com.seer.teach.common.config.mybatis.plugin;

import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import lombok.extern.slf4j.Slf4j;
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.mapping.ParameterMode;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.scripting.xmltags.ForEachSqlNode;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.sql.SQLException;
import java.util.List;
import java.util.Properties;

@Slf4j
public class MybatisSqlFormatPlugin implements InnerInterceptor {

    private Properties properties;

    @Override
    public void beforeQuery(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        try {
            // 执行查询逻辑在MyBatis内部完成，此处无需处理
        } finally {
            formatAndPrintSql(mappedStatement, parameter, boundSql);
        }
    }

    @Override
    public void beforeUpdate(Executor executor, MappedStatement mappedStatement, Object parameter) throws SQLException {
        try {
            // 执行更新逻辑在MyBatis内部完成，此处无需处理
        } finally {
            BoundSql boundSql = mappedStatement.getBoundSql(parameter);
            formatAndPrintSql(mappedStatement, parameter, boundSql);
        }
    }

    /**
     * 格式化并打印SQL语句
     */
    private void formatAndPrintSql(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        try {
            String sql = boundSql.getSql();
            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
            
            if (parameterMappings != null) {
                ObjectFactory objectFactory = new DefaultObjectFactory();
                ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
                ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
                MetaObject metaObject = parameterObject == null ? null : MetaObject
                        .forObject(parameterObject, objectFactory, objectWrapperFactory, reflectorFactory);
                        
                for (int i = 0; i < parameterMappings.size(); i++) {
                    ParameterMapping parameterMapping = parameterMappings.get(i);
                    if (parameterMapping.getMode() != ParameterMode.OUT) {
                        Object value;
                        String propertyName = parameterMapping.getProperty();
                        PropertyTokenizer prop = new PropertyTokenizer(propertyName);
                        if (parameterObject == null) {
                            value = null;
                        } else if (mappedStatement.getConfiguration().getTypeHandlerRegistry().hasTypeHandler(
                                parameterObject.getClass())) {
                            value = parameterObject;
                        } else if (boundSql.hasAdditionalParameter(propertyName)) {
                            value = boundSql.getAdditionalParameter(propertyName);
                        } else if (propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX)
                                && boundSql.hasAdditionalParameter(prop.getName())) {
                            value = boundSql.getAdditionalParameter(prop.getName());
                            if (value != null) {
                                value = MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory).getValue(
                                        propertyName.substring(prop.getName().length()));
                            }
                        } else {
                            value = metaObject == null ? null : metaObject.getValue(propertyName);
                        }
                        if (value != null) {
                            boolean valueIsString = value instanceof String;
                            if (valueIsString && value.toString().indexOf("$") > -1) {
                                value = ((String) value).replaceAll("\\$", "\\\\\\$");
                            }
                            sql = sql.replaceFirst("\\?", valueIsString ? "'" + value + "'" : value.toString());
                        } else {
                            sql = sql.replaceFirst("\\?", "null");
                        }
                    }
                }
            }

            if (properties != null &&
                    "true".equals(properties.getProperty("printSql", "false"))) {
                log.info("执行SQL ID: {}", mappedStatement.getId());
                log.info("执行SQL语句: \n{}", sql);
                log.info("=======End=======");
            }
        } catch (Exception e) {
            log.warn("格式化SQL语句时发生异常", e);
        }
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}