package com.ruoyi.app.task;

import com.ruoyi.app.pay.LinePayRefundService;
import com.ruoyi.app.pay.LinePayCancellationCompensationService;
import com.ruoyi.app.pay.LinePayService;
import com.ruoyi.app.pay.LinePayOfflineService;
import com.ruoyi.system.domain.PosOrderLinePayment;
import com.ruoyi.system.domain.PosOrderLineRefund;
import com.ruoyi.system.service.IPosOrderLinePaymentService;
import com.ruoyi.system.service.IPosOrderLineRefundService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Value;

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

/** Periodically retrieves LINE payment/refund facts and resumes recoverable state. */
@Component
public class LinePayReconcileTask {
    private static final Logger log = LoggerFactory.getLogger(LinePayReconcileTask.class);
    private static final String LOCK_KEY = "lock:line-pay:reconcile";
    @Value("${line-pay.reconcile.batch-size:20}")
    private int batchSize = 20;
    @Value("${line-pay.reconcile.row-lease-seconds:120}")
    private long rowLeaseSeconds = 120L;
    @Value("${line-pay.reconcile.round-budget-seconds:45}")
    private long roundBudgetSeconds = 45L;

    private final IPosOrderLinePaymentService paymentService;
    private final IPosOrderLineRefundService refundService;
    private final LinePayService linePayService;
    private final LinePayOfflineService linePayOfflineService;
    private final LinePayRefundService linePayRefundService;
    private final RedissonClient redissonClient;
    private final LinePayCancellationCompensationService cancellationCompensationService;

    public LinePayReconcileTask(IPosOrderLinePaymentService paymentService,
                                IPosOrderLineRefundService refundService,
                                LinePayService linePayService,
                                LinePayRefundService linePayRefundService,
                                RedissonClient redissonClient,
                                LinePayCancellationCompensationService cancellationCompensationService) {
        this(paymentService, refundService, linePayService, null, linePayRefundService,
                redissonClient, cancellationCompensationService);
    }

    @org.springframework.beans.factory.annotation.Autowired
    public LinePayReconcileTask(IPosOrderLinePaymentService paymentService,
                                IPosOrderLineRefundService refundService,
                                LinePayService linePayService,
                                LinePayOfflineService linePayOfflineService,
                                LinePayRefundService linePayRefundService,
                                RedissonClient redissonClient,
                                LinePayCancellationCompensationService cancellationCompensationService) {
        this.paymentService = paymentService;
        this.refundService = refundService;
        this.linePayService = linePayService;
        this.linePayOfflineService = linePayOfflineService;
        this.linePayRefundService = linePayRefundService;
        this.redissonClient = redissonClient;
        this.cancellationCompensationService = cancellationCompensationService;
    }

    @Scheduled(fixedDelayString = "${line-pay.reconcile.fixed-delay-ms:60000}",
            initialDelayString = "${line-pay.reconcile.initial-delay-ms:60000}")
    public void reconcile() {
        RLock lock = redissonClient.getLock(LOCK_KEY);
        if (!lock.tryLock()) {
            return;
        }
        try {
            reconcileRound();
        } catch (Exception exception) {
            log.error("LINE Pay scheduled reconcile failed", exception);
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    private void reconcileRound() {
        long started = System.currentTimeMillis();
        Date now = new Date(started);
        List<PosOrderLinePayment> payments = paymentService.scanDue(now, batchSize);
        for (PosOrderLinePayment payment : payments) {
            if (roundBudgetExceeded(started)) {
                break;
            }
            String owner = "TASK-" + UUID.randomUUID();
            if (paymentService.claimReconcileLease(payment.getId(), payment.getVersion(), owner,
                    new Date(System.currentTimeMillis() + rowLeaseSeconds * 1000L), new Date()) != 1) {
                continue;
            }
            try {
                if ("OFFLINE".equals(payment.getPaymentMode()) && linePayOfflineService != null) {
                    linePayOfflineService.reconcilePayment(payment.getId(), "TASK");
                } else {
                    linePayService.reconcilePayment(payment.getId(), "TASK", owner);
                }
            } catch (Exception exception) {
                log.warn("LINE Pay payment reconcile failed, paymentId={}", payment.getId());
            }
        }
        repairCancelledPayments();
        List<PosOrderLineRefund> refunds = refundService.scanDue(new Date(), batchSize);
        long refundStarted = System.currentTimeMillis();
        boolean processedRefund = false;
        for (PosOrderLineRefund refund : refunds) {
            if (processedRefund && roundBudgetExceeded(refundStarted)) {
                break;
            }
            try {
                linePayRefundService.requestFullRefund(refund.getPaymentId(), "TASK");
            } catch (Exception exception) {
                log.warn("LINE Pay refund reconcile failed, paymentId={}", refund.getPaymentId());
            }
            processedRefund = true;
        }
    }

    private void repairCancelledPayments() {
        cancellationCompensationService.createMissingRefundIntents(
                paymentService.getCancelledPaidWithoutRefund(batchSize));
    }

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