package xtools.boot.db.mybatis.interceptor;

import com.alibaba.fastjson2.JSONObject;
import jakarta.annotation.Resource;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
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.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.jspecify.annotations.NonNull;
import org.springframework.stereotype.Component;
import xtools.base.config.BaseParams;
import xtools.boot.api.model.dto.log.LogTrack;
import xtools.boot.db.mybatis.config.MyBatisConfig;
import xtools.boot.log.LogBus;
import xtools.boot.log.enums.LogBusBaseType;
import xtools.boot.log.holder.LogTrackHolder;
import xtools.boot.thread.utils.VirtualThreadTaskUtils;
import xtools.core.CollectionUtils;
import xtools.core.enums.LogLevel;
import xtools.core.enums.TimePattern;
import xtools.core.time.InstantUtils;

import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * <p>Title : MyBatisInterceptor</p>
 * <p>Description : MyBatisInterceptor</p>
 * <p>DevelopTools : Idea_x64_v2026.1</p>
 * <p>DevelopSystem : macOS Sequoia 15.7.5</p>
 * <p>Company : org.xujun</p>
 *
 * @author : XuJun
 * @version : 5.0.0
 * @date : 2026/2/11 14:35
 */
@Component
@Intercepts({
        @Signature(type = StatementHandler.class, method = MyBatisInterceptor.QUERY, args = {Statement.class, ResultHandler.class}),
        @Signature(type = StatementHandler.class, method = MyBatisInterceptor.UPDATE, args = {Statement.class}),
        @Signature(type = StatementHandler.class, method = MyBatisInterceptor.BATCH, args = {Statement.class})
})
public class MyBatisInterceptor implements Interceptor, BaseParams {

    /**
     * 查询方法名
     */
    public final static String QUERY = "query";

    /**
     * 更新方法名
     */
    public final static String UPDATE = "update";

    /**
     * 批量方法名
     */
    public final static String BATCH = "batch";

    @Resource
    private MyBatisConfig conf;

    /**
     * 拦截操作
     *
     * @param invocation 调用参数
     * @return 结果
     */
    @Override
    public Object intercept(@NonNull Invocation invocation) throws Throwable {
        LogTrack log = LogTrackHolder.getDefNull();
        // 是否启动监控
        if (!conf.getMonitor() || Objects.isNull(log) || !log.save()) {
            return invocation.proceed();
        }
        // 执行结果
        Object result = null;
        // 异常信息
        Exception err = null;
        // 开始时间
        long startTime = System.currentTimeMillis();
        try {
            result = invocation.proceed();
            return result;
        } catch (Exception e) {
            err = e;
            throw e;
        } finally {
            // 结束时间
            long endTime = System.currentTimeMillis();
            // 获取当前线程日志信息
            LogTrack logTrack = LogTrackHolder.get();
            // 执行结果信息
            final Object execResult = result;
            // 执行异常
            final Exception exception = err;
            // 保存日志
            VirtualThreadTaskUtils.simple(() -> saveLog(logTrack, invocation, execResult, exception, startTime, endTime));
        }
    }

    /**
     * 保存日志信息
     *
     * @param logTrack   日志信息
     * @param invocation 调用参数
     * @param execResult 执行结果
     * @param exception  执行异常
     * @param startTime  开始时间
     * @param endTime    结束时间
     */
    private void saveLog(LogTrack logTrack, Invocation invocation, Object execResult, Exception exception, long startTime, long endTime) {
        // 获取运行参数
        Object runArg = invocation.getArgs()[CP_NUM0];
        // 读写标识
        String rw = QUERY.equals(invocation.getMethod().getName()) ? "读" : "写";
        // 执行时间
        long execTime = endTime - startTime;
        // 是否为慢SQL
        boolean slowSql = execTime >= conf.getSlowTime();
        // SQL语句
        String sql;
        // SQL参数
        List<Object> params = null;
        if (invocation.getTarget() instanceof StatementHandler statementHandler) {
            BoundSql boundSql = statementHandler.getBoundSql();
            sql = boundSql.getSql();
            params = getBoundSqlParams(boundSql);
        } else {
            sql = runArg.toString();
            // 获取完整SQL
            sql = sql.substring(sql.indexOf(CP_COLON) + CP_NUM1);
        }
        // 格式化Sql
        sql = sql
                .replace(CP_COMMA, ", ")
                .replaceAll("\t+", CP_SPACE)
                .replaceAll("\n+", CP_SPACE)
                .replaceAll(" +", CP_SPACE)
                .trim();
        // 分装日志信息
        JSONObject logData = JSONObject.of(
                "startTime", InstantUtils.format(InstantUtils.from(startTime), TimePattern.ALL),
                "execTime", execTime,
                "result", execResult,
                "slowSql", slowSql,
                "sql", sql,
                "params", params
        );
        // 日志级别
        LogLevel level = LogLevel.INFO;
        if (Objects.nonNull(exception)) {
            level = LogLevel.ERROR;
        } else if (slowSql) {
            level = LogLevel.WARN;
        }
        LogBus.init(level, LogBusBaseType.MYBATIS, logTrack).title("SQL/" + rw).data(logData).error(exception).save();
    }

    /**
     * 从BoundSql中获取参数值
     *
     * @param boundSql MyBatis的BoundSql
     * @return 参数集合
     */
    private List<Object> getBoundSqlParams(BoundSql boundSql) {
        if (Objects.isNull(boundSql)) {
            return null;
        }
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (CollectionUtils.isEmpty(parameterMappings)) {
            return null;
        }
        Object parameterObject = boundSql.getParameterObject();
        Map<String, Object> additionalParams = boundSql.getAdditionalParameters();
        List<Object> params = new ArrayList<>(parameterMappings.size());
        MetaObject metaObject = Objects.isNull(parameterObject) ? null : SystemMetaObject.forObject(parameterObject);
        for (ParameterMapping mapping : parameterMappings) {
            String property = mapping.getProperty();
            Object value;
            // 优先从additionalParameters获取(MyBatis-Plus分页参数等动态参数在这里)
            if (additionalParams.containsKey(property)) {
                value = additionalParams.get(property);
            } else if (Objects.nonNull(metaObject) && metaObject.hasGetter(property)) {
                value = metaObject.getValue(property);
            } else if (parameterObject instanceof Map<?, ?> map) {
                try {
                    value = map.get(property);
                } catch (Exception e) {
                    value = "[无法解析:" + property + "]";
                }
            } else {
                value = parameterObject;
            }
            params.add(value);
        }
        return params;
    }

}
