package com.ruoyi.app.pay;

import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.MessageUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.util.concurrent.TimeUnit;

/**
 * Serializes payment-channel creation for the same business order across providers.
 *
 * <p>锁释放在事务提交后（afterCommit）—— 调用方必须处于 {@code @Transactional} 中。若在 finally 直接
 * unlock，锁在方法返回时释放，而事务在 AOP after-returning 才提交，被阻塞的并发请求看不到未提交的活跃
 * 流水 INSERT，会再建一条活跃行（连点堆积）。仿 {@code WalletService.returnPoints} 的 afterCommit 模式。
 */
@Service
public class PaymentCreateGuardService {
    private static final Logger log = LoggerFactory.getLogger(PaymentCreateGuardService.class);
    private static final String PREFIX = "lock:payment:create:";
    /** 获取锁最长等待（秒）：超时让调用方快速失败，避免用户长时间阻塞。 */
    private static final long LOCK_WAIT_SECONDS = 3L;

    private final RedissonClient redissonClient;

    public PaymentCreateGuardService(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    public <T> T withLock(String ddId, LockedCall<T> call) {
        if (ddId == null || ddId.trim().isEmpty()) {
            throw new IllegalArgumentException("ddId is required");
        }
        RLock lock = redissonClient.getLock(PREFIX + ddId.trim());
        boolean acquired;
        try {
            // tryLock(waitTime, unit)：等待 3s，获取后由 Redisson 看门狗自动续期（未指定 leaseTime），
            // 避免事务未提交锁先过期；锁最终在 afterCommit 释放。
            acquired = lock.tryLock(LOCK_WAIT_SECONDS, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new ServiceException(MessageUtils.message("no.system.busy.try.again"));
        }
        if (!acquired) {
            // 3 秒等锁失败 = 同订单有并发支付创建，这是"系统繁忙"提示的唯一现场线索
            log.warn("Payment create lock not acquired within {}s, ddId={}", LOCK_WAIT_SECONDS, ddId);
            throw new ServiceException(MessageUtils.message("no.system.busy.try.again"));
        }
        boolean deferredUnlock = false;
        try {
            T result = call.call();
            if (TransactionSynchronizationManager.isSynchronizationActive()) {
                TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                    @Override
                    public void afterCompletion(int status) {
                        if (lock.isHeldByCurrentThread()) {
                            lock.unlock();
                        }
                    }
                });
                deferredUnlock = true;
            }
            return result;
        } finally {
            if (!deferredUnlock && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    @FunctionalInterface
    public interface LockedCall<T> {
        T call();
    }
}
