package com.rj.service.support;

import java.sql.SQLException;
import java.time.LocalDate;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;

/**
 * 同一租户、同一报表日期的 lb_daily_user_trade_report 写操作串行化，降低并发 DELETE/INSERT 死锁概率。
 */
public final class LbDailyUserTradeReportWriteSupport {

    private static final ConcurrentHashMap<String, ReentrantLock> LOCKS = new ConcurrentHashMap<>();
    private static final int MAX_DEADLOCK_RETRIES = 3;

    private LbDailyUserTradeReportWriteSupport() {
    }

    public static void runExclusive(String tenantId, LocalDate reportDate, Runnable action) {
        runExclusiveReturning(tenantId, reportDate, () -> {
            action.run();
            return null;
        });
    }

    public static <T> T runExclusiveReturning(String tenantId, LocalDate reportDate, Supplier<T> action) {
        String key = lockKey(tenantId, reportDate);
        ReentrantLock lock = LOCKS.computeIfAbsent(key, ignored -> new ReentrantLock());
        lock.lock();
        try {
            return runWithDeadlockRetry(action);
        } finally {
            lock.unlock();
        }
    }

    public static void runWithDeadlockRetry(Runnable action) {
        runWithDeadlockRetry(() -> {
            action.run();
            return null;
        });
    }

    public static <T> T runWithDeadlockRetry(Supplier<T> action) {
        RuntimeException last = null;
        for (int attempt = 1; attempt <= MAX_DEADLOCK_RETRIES; attempt++) {
            try {
                return action.get();
            } catch (RuntimeException ex) {
                last = ex;
                if (attempt >= MAX_DEADLOCK_RETRIES || !isDeadlock(ex)) {
                    throw ex;
                }
                sleepBriefly(attempt);
            }
        }
        throw last;
    }

    public static boolean isDeadlock(Throwable throwable) {
        for (Throwable current = throwable; current != null; current = current.getCause()) {
            if (current instanceof SQLException sqlException) {
                if (sqlException.getErrorCode() == 1213 || "40001".equals(sqlException.getSQLState())) {
                    return true;
                }
            }
            String message = current.getMessage();
            if (message != null && message.contains("Deadlock found")) {
                return true;
            }
        }
        return false;
    }

    private static String lockKey(String tenantId, LocalDate reportDate) {
        return tenantId.trim() + "|" + reportDate;
    }

    private static void sleepBriefly(int attempt) {
        try {
            Thread.sleep(50L * attempt);
        } catch (InterruptedException interrupted) {
            Thread.currentThread().interrupt();
            throw new IllegalStateException("报表写入被中断", interrupted);
        }
    }
}
