package com.ruoyi.app.omgpay;

import com.ruoyi.app.omgpay.dto.OmgQueryPaymentResponse;
import com.ruoyi.system.omgpay.domain.OmgPaymentAttempt;
import com.ruoyi.system.omgpay.service.IOmgPaymentAttemptService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;

/** Periodically queries due CREATED attempts to recover payment callbacks that were lost. */
@Component
public class OmgPaymentAutoCompensationTask {
    private static final Logger log = LoggerFactory.getLogger(OmgPaymentAutoCompensationTask.class);
    private static final String LOCK_KEY = "lock:omgpay:automatic-compensation";

    @Value("${omgpay.reconcile.batch-size:20}")
    int batchSize = 20;
    @Value("${omgpay.reconcile.retry-delay-ms:300000}")
    long retryDelayMs = 300000L;
    @Value("${omgpay.reconcile.round-budget-seconds:45}")
    long roundBudgetSeconds = 45L;

    private final IOmgPaymentAttemptService attempts;
    private final OmgPaymentQueryService queryService;
    private final RedissonClient redissonClient;

    public OmgPaymentAutoCompensationTask(IOmgPaymentAttemptService attempts,
                                          OmgPaymentQueryService queryService,
                                          RedissonClient redissonClient) {
        this.attempts = attempts;
        this.queryService = queryService;
        this.redissonClient = redissonClient;
    }

    @Scheduled(fixedDelayString = "${omgpay.reconcile.fixed-delay-ms:60000}",
            initialDelayString = "${omgpay.reconcile.initial-delay-ms:60000}")
    public void reconcile() {
        RLock lock = null;
        try {
            lock = redissonClient.getLock(LOCK_KEY);
            if (!lock.tryLock()) {
                return;
            }
            reconcileRound();
        } catch (Exception exception) {
            log.error("OMG automatic compensation round failed", exception);
        } finally {
            if (lock != null && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    void reconcileRound() {
        long started = System.currentTimeMillis();
        Date now = new Date(started);
        List<OmgPaymentAttempt> dueAttempts = attempts.scanDueCreated(now, batchSize);
        int reserved = 0;
        int completed = 0;
        for (OmgPaymentAttempt attempt : dueAttempts) {
            if (roundBudgetExceeded(started)) {
                break;
            }
            Date nextQueryTime = new Date(System.currentTimeMillis() + retryDelayMs);
            if (attempts.reserveDueQuery(attempt.getId(), new Date(), nextQueryTime) != 1) {
                continue;
            }
            reserved++;
            try {
                OmgQueryPaymentResponse response = queryService.reconcile(attempt);
                completed++;
                log.info("OMG automatic compensation processed attemptId={}, orderId={}, merchantTradeNo={}, "
                                + "gatewayTradeStatus={}, localStatus={}",
                        attempt.getId(), OmgPaymentController.safeLogOrderId(attempt.getDdId()),
                        OmgPaymentCreateService.maskMerchantTradeNo(attempt.getMerchantTradeNo()),
                        response.tradeStatus(), response.status());
            } catch (Exception exception) {
                log.error("OMG automatic compensation query failed attemptId={}, orderId={}, merchantTradeNo={}",
                        attempt.getId(), OmgPaymentController.safeLogOrderId(attempt.getDdId()),
                        OmgPaymentCreateService.maskMerchantTradeNo(attempt.getMerchantTradeNo()), exception);
            }
        }
        if (!dueAttempts.isEmpty()) {
            log.info("OMG automatic compensation round completed scanned={}, reserved={}, completed={}",
                    dueAttempts.size(), reserved, completed);
        }
    }

    private boolean roundBudgetExceeded(long started) {
        return System.currentTimeMillis() - started >= roundBudgetSeconds * 1000L;
    }
}
