package com.ruoyi.app.pay;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.system.domain.PosOrder;
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 com.ruoyi.system.service.IPosOrderService;
import com.ruoyi.system.mapper.PosOrderMapper;
import com.ruoyi.app.order.OrderLifecycleService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

/** Commits gateway-proven payment facts and creates cancellation compensation intents. */
@Service
public class LinePayFactService {
    private final IPosOrderService orderService;
    private final IPosOrderLinePaymentService paymentService;
    private final IPosOrderLineRefundService refundService;
    private final PosOrderMapper orderMapper;
    private final LinePayOrderNotificationService notificationService;
    private final OrderLifecycleService orderLifecycleService;

    public LinePayFactService(IPosOrderService orderService,
                              IPosOrderLinePaymentService paymentService,
                              IPosOrderLineRefundService refundService) {
        this(orderService, paymentService, refundService, null, null, null);
    }

    public LinePayFactService(IPosOrderService orderService,
                              IPosOrderLinePaymentService paymentService,
                              IPosOrderLineRefundService refundService,
                              PosOrderMapper orderMapper) {
        this(orderService, paymentService, refundService, orderMapper, null, null);
    }

    @org.springframework.beans.factory.annotation.Autowired
    public LinePayFactService(IPosOrderService orderService,
                              IPosOrderLinePaymentService paymentService,
                              IPosOrderLineRefundService refundService,
                              PosOrderMapper orderMapper,
                              LinePayOrderNotificationService notificationService,
                              OrderLifecycleService orderLifecycleService) {
        this.orderService = orderService;
        this.paymentService = paymentService;
        this.refundService = refundService;
        this.orderMapper = orderMapper;
        this.notificationService = notificationService;
        this.orderLifecycleService = orderLifecycleService;
    }

    @Transactional(rollbackFor = Exception.class)
    public void applyPaidFact(Long paymentId, String transactionId,
                              String paymentProvider, Date payTime) {
        applyPaidFactInternal(paymentId, transactionId, paymentProvider, payTime, true);
    }

    private void applyPaidFactInternal(Long paymentId, String transactionId,
                                       String paymentProvider, Date payTime,
                                       boolean notifyOrder) {
        PosOrderLinePayment payment = paymentService.getById(paymentId);
        if (payment == null) {
            throw new ServiceException("LINE Pay payment not found");
        }
        int marked = paymentService.markPaid(paymentId, payment.getVersion(), payment.getStatus(),
                transactionId, paymentProvider, payTime);
        if (marked != 1 && !"PAID".equals(payment.getStatus())) {
            throw new ServiceException("LINE Pay payment state changed concurrently");
        }
        PosOrder order = orderService.getOne(new QueryWrapper<PosOrder>()
                .eq("dd_id", payment.getDdId()).last("FOR UPDATE"));
        if (order == null) {
            throw new ServiceException("LINE Pay order not found");
        }
        if (orderMapper != null) {
            int orderMarked = orderMapper.markLinePaid(payment.getDdId(), payment.getId());
            if (orderMarked == 1 && !Long.valueOf(4L).equals(order.getState())
                    && notifyOrder && notificationService != null) {
                order.setPayStatus(1L);
                notificationService.paymentCaptured(order);
            } else if (orderMarked != 1 && !Long.valueOf(4L).equals(order.getState())
                    && !isOnlyRecordedLinePayment(order, payment)) {
                refundService.createIfAbsent(payment, "DUPLICATE_PAYMENT");
            }
        } else {
            orderService.update(new UpdateWrapper<PosOrder>()
                    .eq("id", order.getId())
                    .set("pay_status", 1L));
        }
        payment.setStatus("PAID");
        payment.setTransactionId(transactionId);
        if (Long.valueOf(4L).equals(order.getState())) {
            refundService.createIfAbsent(payment, "TASK");
        }
    }

    /** Records the captured gateway fact but blocks fulfillment for ambiguous refund evidence. */
    @Transactional(rollbackFor = Exception.class)
    public void applyPaidWithRefundReviewFact(Long paymentId, String transactionId,
                                              String paymentProvider, Date payTime) {
        PosOrderLinePayment payment = paymentService.getById(paymentId);
        if (payment == null) {
            throw new ServiceException("LINE Pay payment not found");
        }
        int marked = paymentService.markPaid(paymentId, payment.getVersion(), payment.getStatus(),
                transactionId, paymentProvider, payTime);
        if (marked != 1 && !"PAID".equals(payment.getStatus())) {
            throw new ServiceException("LINE Pay payment state changed concurrently");
        }
        payment.setStatus("PAID");
        payment.setTransactionId(transactionId);
        PosOrderLineRefund refund = refundService.createIfAbsent(payment, "RETRIEVE");
        if (refund == null) {
            throw new ServiceException("LINE Pay refund review state could not be created");
        }
        if (!"MANUAL_REVIEW".equals(refund.getStatus()) && !"REFUNDED".equals(refund.getStatus())
                && refundService.markManualReviewFromEvidence(refund.getId(), refund.getVersion(),
                refund.getStatus()) != 1) {
            throw new ServiceException("LINE Pay refund review state changed concurrently");
        }
    }

    /** Persists captured mismatch evidence and its unique refund intent atomically. */
    @Transactional(rollbackFor = Exception.class)
    public boolean applyAmountMismatchFact(Long paymentId, Long version, String expectedStatus,
                                           String transactionId, String paymentProvider,
                                           Integer capturedAmount, Date payTime) {
        int marked = paymentService.markOfflineEvidence(paymentId, version, expectedStatus,
                "AMOUNT_MISMATCH", transactionId, paymentProvider, capturedAmount, payTime);
        if (marked != 1) {
            return false;
        }
        PosOrderLinePayment current = paymentService.getById(paymentId);
        if (current == null || !"AMOUNT_MISMATCH".equals(current.getStatus())
                || !capturedAmount.equals(current.getCapturedAmount())) {
            throw new ServiceException("LINE Pay captured amount mismatch state is unavailable");
        }
        refundService.createIfAbsent(current, "AMOUNT_MISMATCH");
        return true;
    }

    private boolean isOnlyRecordedLinePayment(PosOrder order, PosOrderLinePayment payment) {
        if (!"3".equals(order.getPayType()) || !Long.valueOf(1L).equals(order.getPayStatus())) {
            return false;
        }
        return paymentService.getByDdId(payment.getDdId()).stream()
                .filter(row -> "PAID".equals(row.getStatus()))
                .allMatch(row -> payment.getId().equals(row.getId()));
    }

    @Transactional(rollbackFor = Exception.class)
    public void applyRefundFact(Long paymentId) {
        PosOrderLinePayment payment = paymentService.getById(paymentId);
        if (payment == null || !("PAID".equals(payment.getStatus())
                || "AMOUNT_MISMATCH".equals(payment.getStatus()))) {
            throw new ServiceException("LINE Pay payment not found or not captured");
        }
        if ("AMOUNT_MISMATCH".equals(payment.getStatus())) {
            return;
        }
        if (orderMapper != null) {
            int orderMarked = orderMapper.markLineRefunded(payment.getDdId(), payment.getId());
            PosOrder latest = orderService.getOne(new QueryWrapper<PosOrder>()
                    .eq("dd_id", payment.getDdId()).last("FOR UPDATE"));
            if (orderMarked == 1 || latest != null && Long.valueOf(2L).equals(latest.getPayStatus())) {
                if (orderLifecycleService != null && latest != null) {
                    orderLifecycleService.finalizeSystemLineRefund(latest.getId());
                }
            } else if (latest == null || LinePayService.PAY_TYPE_LINE.equals(latest.getPayType())
                    && !hasAnotherUnrefundedPayment(payment)) {
                throw new ServiceException("LINE Pay refund order state requires reconciliation");
            }
        } else {
            orderService.update(new UpdateWrapper<PosOrder>()
                    .eq("dd_id", payment.getDdId())
                    .eq("pay_status", 1L)
                    .set("pay_status", 2L));
        }
    }

    private boolean hasAnotherUnrefundedPayment(PosOrderLinePayment refundedPayment) {
        return paymentService.getByDdId(refundedPayment.getDdId()).stream()
                .filter(row -> "PAID".equals(row.getStatus()))
                .filter(row -> !refundedPayment.getId().equals(row.getId()))
                .anyMatch(row -> {
                    PosOrderLineRefund refund = refundService.getByPaymentId(row.getId());
                    return refund == null || !"REFUNDED".equals(refund.getStatus());
                });
    }

    @Transactional(rollbackFor = Exception.class)
    public void applyRefundedFact(Long refundId, Long paymentId, Long refundVersion,
                                  String refundTransactionId, Date refundTime) {
        if (refundService.markRefunded(refundId, refundVersion,
                refundTransactionId, refundTime) != 1) {
            throw new ServiceException("LINE Pay refund state changed concurrently");
        }
        applyRefundFact(paymentId);
    }

    @Transactional(rollbackFor = Exception.class)
    public void applyPaidAndRefundedFact(Long paymentId, String transactionId,
                                         String paymentProvider, Date payTime,
                                         String refundTransactionId, Date refundTime) {
        applyPaidFactInternal(paymentId, transactionId, paymentProvider, payTime, false);
        PosOrderLinePayment payment = paymentService.getById(paymentId);
        PosOrderLineRefund refund = refundService.createIfAbsent(payment, "RETRIEVE");
        if ("REFUNDED".equals(refund.getStatus())) {
            applyRefundFact(paymentId);
            return;
        }
        if (refundService.markRefundedFromEvidence(refund.getId(), refund.getVersion(),
                refund.getStatus(), refundTransactionId, refundTime) != 1) {
            throw new ServiceException("LINE Pay refund evidence state changed concurrently");
        }
        applyRefundFact(paymentId);
    }
}
