package xtools.boot.db.mybatis.interceptor;

import com.alibaba.druid.pool.DruidPooledPreparedStatement;
import com.alibaba.druid.proxy.jdbc.JdbcParameter;
import com.alibaba.druid.proxy.jdbc.PreparedStatementProxyImpl;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import jakarta.annotation.Resource;
import org.apache.ibatis.executor.statement.StatementHandler;
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.session.ResultHandler;
import org.jspecify.annotations.NonNull;
import org.springframework.stereotype.Component;
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.PreparedStatement;
import java.sql.Statement;
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})
})
public class MyBatisInterceptor implements Interceptor {

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

    @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 {
            // 获取当前线程日志信息
            LogTrack logTrack = LogTrackHolder.get();
            // 结束时间
            long endTime = System.currentTimeMillis();
            // 执行时间
            long execTime = endTime - startTime;
            // 执行结果信息
            final Object execResult = result;
            // 转换参数
            Throwable ft = err;
            // 处理SQL日志
            VirtualThreadTaskUtils.simple(() -> {
                // 获取运行参数
                Object runArg = invocation.getArgs()[0];
                // 读写标识
                String rw = QUERY.equals(invocation.getMethod().getName()) ? "读" : "写";
                // 日志信息
                JSONObject logData = JSONObject.of(
                        "startTime", InstantUtils.format(InstantUtils.from(startTime), TimePattern.ALL),
                        "execTime", execTime,
                        "result", execResult,
                        "rw", rw
                );
                // 是否为慢SQL
                logData.put("slowSql", execTime >= conf.getSlowTime());
                // 日志分析
                this.analysisLog(logTrack, logData, runArg, ft);
            });
        }
    }

    /**
     * 日志分析
     *
     * @param logTrack 日志信息
     * @param log      日志信息
     * @param runArg   运行参数
     * @param t        异常
     */
    private void analysisLog(LogTrack logTrack, JSONObject log, Object runArg, Throwable t) {
        try {
            // 获取sql信息
            JSONObject sqlInfo = this.getSqlInfo(runArg, logTrack, log);
            if (CollectionUtils.isNotEmpty(sqlInfo)) {
                log.putAll(sqlInfo);
            }
        } catch (Exception e) {
            t = e;
        } finally {
            this.execLog(logTrack, log, t);
        }

    }

    /**
     * 获取SQL信息
     *
     * @param runArg 运行参数
     * @param log    日志信息
     * @return SQL信息
     */
    private JSONObject getSqlInfo(Object runArg, LogTrack logTrack, JSONObject log) {
        // 执行SQL
        String sql;
        // 参数
        JSONArray params = new JSONArray();

        // 个性化参数获取方式
        if (DruidPooledPreparedStatement.class.equals(runArg.getClass())) {
            // 开启druid监控获取参数方式
            DruidPooledPreparedStatement statement = (DruidPooledPreparedStatement) runArg;
            sql = statement.getSql();
            try {
                JSONArray dp = this.getParams(statement);
                if (dp != null) {
                    params = dp;
                }
            } catch (Exception e) {
                this.execLog(logTrack, log, e);
            }
        } else {
            sql = runArg.toString();
            // 获取完整SQL
            sql = sql.substring(sql.indexOf(":") + 1);
        }
        // 格式化Sql
        sql = sql.replaceAll(",", ", ").replaceAll("\t+", " ").replaceAll("\n+", " ").replaceAll(" +", " ").trim();
        // 结果封装
        return JSONObject.of("sql", sql, "params", params);
    }

    /**
     * 获取参数
     *
     * @param statement DruidPooledPreparedStatement
     * @return 参数集合
     */
    private JSONArray getParams(DruidPooledPreparedStatement statement) {
        if (statement == null) {
            return null;
        }
        PreparedStatement ps = statement.getRawStatement();
        if (ps == null) {
            return null;
        }
        if (!PreparedStatementProxyImpl.class.equals(ps.getClass())) {
            return null;
        }
        PreparedStatementProxyImpl psp = (PreparedStatementProxyImpl) ps;
        int parametersSize = psp.getParametersSize();
        if (parametersSize <= 0) {
            return null;
        }
        Map<Integer, JdbcParameter> paramMap = psp.getParameters();
        if (CollectionUtils.isEmpty(paramMap)) {
            return null;
        }
        // 参数
        JSONArray params = new JSONArray();
        paramMap.forEach((key, value) -> params.add(value.getValue()));
        return params;
    }

    /**
     * 处理SQL日志
     *
     * @param logTrack 日志信息
     * @param log      日志信息
     * @param t        异常
     */
    private void execLog(LogTrack logTrack, JSONObject log, Throwable t) {
        // 是否为慢SQL
        boolean slowSql = log.getBooleanValue("slowSql", false);
        // 日志级别
        LogLevel level = LogLevel.INFO;
        if (t != null) {
            level = LogLevel.ERROR;
        } else if (slowSql) {
            level = LogLevel.WARN;
        }
        // 获取读写
        String rw = log.getString("rw");
        log.remove("rw");
        LogBus.init(level, LogBusBaseType.MYBATIS, logTrack).title("SQL/" + rw).data(log).error(t).save();
    }

}
