package com.ruoyi.app.order;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.ruoyi.app.order.dto.AdminOrderActionRequest;
import com.ruoyi.app.order.dto.AdminOrderStatusContext;
import com.ruoyi.app.order.dto.AdminOrderStatusUpdateRequest;
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.service.IPointsTransactionService;
import com.ruoyi.system.service.IUserWalletService;
import com.ruoyi.system.utils.OrderLogHelper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import org.mockito.InOrder;

class OrderLifecycleServiceTest {

    private IPosOrderService posOrderService;
    private OrderService billingService;
    private OrderLogHelper orderLogHelper;
    private IUserWalletService userWalletService;
    private IPointsTransactionService pointsTransactionService;
    private RiderDeliveryLockService riderDeliveryLockService;
    private RiderDeliveryExclusivityService riderDeliveryExclusivityService;
    private OrderLifecycleService service;

    @BeforeEach
    void setUp() {
        posOrderService = mock(IPosOrderService.class);
        billingService = mock(OrderService.class);
        orderLogHelper = mock(OrderLogHelper.class);
        userWalletService = mock(IUserWalletService.class);
        pointsTransactionService = mock(IPointsTransactionService.class);
        riderDeliveryLockService = mock(RiderDeliveryLockService.class);
        riderDeliveryExclusivityService = mock(RiderDeliveryExclusivityService.class);
        when(riderDeliveryLockService.withLock(any(), any())).thenAnswer(invocation -> {
            RiderDeliveryLockService.LockedCall<?> call = invocation.getArgument(1);
            return call.call();
        });
        service = new OrderLifecycleService(posOrderService, billingService, orderLogHelper,
                userWalletService, pointsTransactionService, null, null,
                riderDeliveryLockService, riderDeliveryExclusivityService);
    }

    @Test
    void exposesOnlyLegalNextOrderAndDeliveryStates() {
        PosOrder order = deliveryOrder();
        when(posOrderService.getById(10L)).thenReturn(order);
        AdminOrderStatusContext context = service.getStatusContext(10L);

        assertEquals(List.of(2L), context.getAllowedOrderStates());
        assertEquals(List.of(2L, 3L), context.getAllowedDeliveryStatuses());
        assertFalse(context.getCanConfirmOfflinePayment());
    }

    @Test
    void blocksPlatformProgressForUnpaidLineOrderButNotHistoricalPayTypeThree() {
        PosOrder order = pickupOrder("3", 0L);
        order.setState(1L);
        when(posOrderService.getById(20L)).thenReturn(order);
        IPosOrderLinePaymentService linePaymentService = mock(IPosOrderLinePaymentService.class);
        IPosOrderLineRefundService lineRefundService = mock(IPosOrderLineRefundService.class);
        PosOrderLinePayment attempt = new PosOrderLinePayment();
        attempt.setId(9L);
        attempt.setDdId(order.getDdId());
        attempt.setStatus("WAITING_AUTH");
        when(linePaymentService.getByDdId(order.getDdId())).thenReturn(List.of(attempt));
        OrderLifecycleService lineService = new OrderLifecycleService(posOrderService, billingService,
                orderLogHelper, userWalletService, pointsTransactionService,
                linePaymentService, lineRefundService);

        assertEquals(List.of(1L, 4L), lineService.getStatusContext(20L).getAllowedOrderStates());

        when(linePaymentService.getByDdId(order.getDdId())).thenReturn(List.of());
        assertEquals(List.of(1L, 2L, 4L), lineService.getStatusContext(20L).getAllowedOrderStates());
    }

    @Test
    void adminContextSelectsUniqueUnrefundedPaidAttemptBeforeActiveRetry() {
        PosOrder order = pickupOrder("3", 1L);
        when(posOrderService.getById(20L)).thenReturn(order);
        IPosOrderLinePaymentService linePaymentService = mock(IPosOrderLinePaymentService.class);
        IPosOrderLineRefundService lineRefundService = mock(IPosOrderLineRefundService.class);
        PosOrderLinePayment paid = new PosOrderLinePayment();
        paid.setId(8L);
        paid.setDdId(order.getDdId());
        paid.setStatus("PAID");
        PosOrderLinePayment active = new PosOrderLinePayment();
        active.setId(9L);
        active.setDdId(order.getDdId());
        active.setStatus("WAITING_AUTH");
        active.setActiveDdId(order.getDdId());
        when(linePaymentService.getByDdId(order.getDdId())).thenReturn(List.of(active, paid));
        OrderLifecycleService lineService = new OrderLifecycleService(posOrderService, billingService,
                orderLogHelper, userWalletService, pointsTransactionService,
                linePaymentService, lineRefundService);

        AdminOrderStatusContext context = lineService.getStatusContext(20L);

        assertEquals(8L, context.getLinePaymentId());
        assertTrue(context.getCanRefundLine());
    }

    @Test
    void completedLineOrderCannotStartExternalRefund() {
        PosOrder order = pickupOrder("3", 1L);
        order.setState(3L);
        when(posOrderService.getById(20L)).thenReturn(order);
        IPosOrderLinePaymentService linePaymentService = mock(IPosOrderLinePaymentService.class);
        IPosOrderLineRefundService lineRefundService = mock(IPosOrderLineRefundService.class);
        PosOrderLinePayment paid = new PosOrderLinePayment();
        paid.setId(8L);
        paid.setStatus("PAID");
        when(linePaymentService.getByDdId(order.getDdId())).thenReturn(List.of(paid));
        OrderLifecycleService lineService = new OrderLifecycleService(posOrderService, billingService,
                orderLogHelper, userWalletService, pointsTransactionService,
                linePaymentService, lineRefundService);

        assertFalse(lineService.getStatusContext(20L).getCanRefundLine());
        assertThrows(ServiceException.class, () -> lineService.reserveLineRefund(20L, "ADMIN"));
    }

    @Test
    void lineRefundReservationBlocksConcurrentOrderCompletion() {
        PosOrder order = pickupOrder("3", 1L);
        order.setState(2L);
        IPosOrderLinePaymentService linePaymentService = mock(IPosOrderLinePaymentService.class);
        IPosOrderLineRefundService lineRefundService = mock(IPosOrderLineRefundService.class);
        PosOrderLinePayment paid = new PosOrderLinePayment();
        paid.setId(8L);
        paid.setDdId(order.getDdId());
        paid.setStatus("PAID");
        PosOrderLineRefund reservation = new PosOrderLineRefund();
        reservation.setPaymentId(paid.getId());
        when(posOrderService.getOne(any(Wrapper.class))).thenReturn(order);
        when(posOrderService.getById(order.getId())).thenReturn(order);
        when(linePaymentService.getByDdId(order.getDdId())).thenReturn(List.of(paid));
        when(lineRefundService.createIfAbsent(paid, "ADMIN")).thenReturn(reservation);
        when(posOrderService.update(any(PosOrder.class), any(Wrapper.class))).thenReturn(false);
        OrderLifecycleService lineService = new OrderLifecycleService(posOrderService, billingService,
                orderLogHelper, userWalletService, pointsTransactionService,
                linePaymentService, lineRefundService);

        assertEquals(8L, lineService.reserveLineRefund(order.getId(), "ADMIN"));
        assertThrows(ServiceException.class, () -> lineService.completePaidLineOrder(order.getId()));
        verify(lineRefundService).createIfAbsent(paid, "ADMIN");
        verify(billingService, never()).setSanghuBilling(any());
    }

    @Test
    void completingPaidLineOrderRunsCompletionSideEffectsOnce() {
        PosOrder order = pickupOrder("3", 1L);
        order.setState(2L);
        IPosOrderLinePaymentService linePaymentService = mock(IPosOrderLinePaymentService.class);
        IPosOrderLineRefundService lineRefundService = mock(IPosOrderLineRefundService.class);
        PosOrderLinePayment paid = new PosOrderLinePayment();
        paid.setId(8L);
        paid.setDdId(order.getDdId());
        paid.setStatus("PAID");
        when(posOrderService.getOne(any(Wrapper.class))).thenReturn(order);
        when(linePaymentService.getByDdId(order.getDdId())).thenReturn(List.of(paid));
        when(posOrderService.update(any(PosOrder.class), any(Wrapper.class))).thenReturn(true);
        OrderLifecycleService lineService = new OrderLifecycleService(posOrderService, billingService,
                orderLogHelper, userWalletService, pointsTransactionService,
                linePaymentService, lineRefundService);

        lineService.completePaidLineOrder(order.getId());

        verify(billingService).setSanghuBilling(order);
        verify(billingService, never()).setQishouBilling(any());
    }

    @Test
    void riderAssignmentAndPickupRequireLegalStateAndOwnership() {
        PosOrder order = deliveryOrder();
        order.setState(0L);
        order.setDeliveryStatus(0L);
        order.setQsId(null);
        when(posOrderService.getById(10L)).thenReturn(order);
        when(posOrderService.update(any(PosOrder.class), any(Wrapper.class))).thenReturn(true);

        order.setPayStatus(0L);
        assertThrows(ServiceException.class,
                () -> service.acceptDeliveryByRider(10L, 88L));

        order.setPayStatus(1L);
        order.setState(2L);
        assertThrows(ServiceException.class,
                () -> service.acceptDeliveryByRider(10L, 88L));

        order.setState(0L);
        service.acceptDeliveryByRider(10L, 88L);
        order.setQsId(88L);
        order.setDeliveryStatus(1L);
        order.setState(2L);
        service.pickupDeliveryByRider(10L, 88L, "proof");

        order.setQsId(99L);
        assertThrows(ServiceException.class,
                () -> service.pickupDeliveryByRider(10L, 88L, "proof"));
    }

    @Test
    void foodAcceptUsesSharedRiderLockAndChecksExclusivityBeforeCas() {
        PosOrder order = deliveryOrder();
        order.setState(0L);
        order.setDeliveryStatus(0L);
        order.setQsId(null);
        when(posOrderService.getById(10L)).thenReturn(order);
        when(posOrderService.update(any(PosOrder.class), any(Wrapper.class))).thenReturn(true);

        service.acceptDeliveryByRider(10L, 88L);

        InOrder calls = inOrder(riderDeliveryLockService, riderDeliveryExclusivityService, posOrderService);
        calls.verify(riderDeliveryLockService)
                .withLock(eq(88L), any(RiderDeliveryLockService.LockedCall.class));
        calls.verify(riderDeliveryExclusivityService).assertCanAcceptFood(88L);
        calls.verify(posOrderService).update(any(PosOrder.class), any(Wrapper.class));
    }

    @Test
    void foodExclusiveConflictDoesNotUpdateOrder() {
        doThrow(new ServiceException("exclusive"))
                .when(riderDeliveryExclusivityService).assertCanAcceptFood(88L);

        assertThrows(ServiceException.class, () -> service.acceptDeliveryByRider(10L, 88L));

        verify(posOrderService, never()).update(any(PosOrder.class), any(Wrapper.class));
    }

    @Test
    void marksDeliveryAsDeliveredAndCompletesExactlyOnce() {
        PosOrder order = deliveryOrder();
        when(posOrderService.getById(10L)).thenReturn(order);
        doAnswer(invocation -> {
            PosOrder update = invocation.getArgument(0);
            order.setState(update.getState());
            order.setDeliveryStatus(update.getDeliveryStatus());
            order.setPayStatus(update.getPayStatus());
            order.setAfterSaleStatus(update.getAfterSaleStatus());
            order.setSdTime(update.getSdTime());
            return true;
        }).when(posOrderService).update(any(PosOrder.class), any(Wrapper.class));

        AdminOrderStatusUpdateRequest request = statusRequest(2L, 2L, 1L, 0L);
        request.setTargetDeliveryStatus(3L);
        request.setReason("骑手端漏操作");

        AdminOrderStatusContext context = service.updateStatus(10L, request, 99L, "平台管理员");

        assertEquals(3L, context.getState());
        assertEquals(3L, context.getDeliveryStatus());
        assertNotNull(context.getSdTime());
        verify(billingService).setSanghuBilling(order);
        verify(billingService).setQishouBilling(order);
        verify(orderLogHelper).logSync(anyString(), anyInt(), anyLong(), anyString(), anyString());
    }

    @Test
    void staleSnapshotHasNoSideEffects() {
        PosOrder order = deliveryOrder();
        order.setPayStatus(0L);
        when(posOrderService.getById(10L)).thenReturn(order);
        AdminOrderStatusUpdateRequest request = statusRequest(2L, 2L, 1L, 0L);
        request.setTargetDeliveryStatus(3L);
        request.setReason("陈旧页面提交");

        ServiceException error = assertThrows(ServiceException.class,
                () -> service.updateStatus(10L, request, 99L, "平台管理员"));

        assertTrue(error.getMessage().contains("状态已变化"));
        verify(posOrderService, never()).update(any(PosOrder.class), any(Wrapper.class));
        verify(billingService, never()).setSanghuBilling(any());
        verify(orderLogHelper, never()).logSync(anyString(), anyInt(), any(), anyString(), anyString());
    }

    @Test
    void casFailureAfterReadHasNoSideEffects() {
        PosOrder order = deliveryOrder();
        when(posOrderService.getById(10L)).thenReturn(order);
        when(posOrderService.update(any(PosOrder.class), any(Wrapper.class))).thenReturn(false);
        AdminOrderStatusUpdateRequest request = statusRequest(2L, 2L, 1L, 0L);
        request.setTargetDeliveryStatus(3L);
        request.setReason("并发提交");

        ServiceException error = assertThrows(ServiceException.class,
                () -> service.updateStatus(10L, request, 99L, "平台管理员"));

        assertTrue(error.getMessage().contains("状态已变化"));
        verify(billingService, never()).setSanghuBilling(any());
        verify(orderLogHelper, never()).logSync(anyString(), anyInt(), any(), anyString(), anyString());
    }

    @Test
    void rejectsDeliveryRollback() {
        PosOrder order = deliveryOrder();
        when(posOrderService.getById(10L)).thenReturn(order);
        AdminOrderStatusUpdateRequest request = statusRequest(2L, 2L, 1L, 0L);
        request.setTargetDeliveryStatus(1L);
        request.setReason("错误回退");

        assertThrows(ServiceException.class,
                () -> service.updateStatus(10L, request, 99L, "平台管理员"));
        verify(posOrderService, never()).update(any(PosOrder.class), any(Wrapper.class));
    }

    @Test
    void confirmsOnlyOfflinePayment() {
        PosOrder order = pickupOrder("1", 0L);
        when(posOrderService.getById(20L)).thenReturn(order);
        doAnswer(invocation -> {
            PosOrder update = invocation.getArgument(0);
            order.setPayStatus(update.getPayStatus());
            return true;
        }).when(posOrderService).update(any(PosOrder.class), any(Wrapper.class));

        AdminOrderStatusContext context = service.confirmOfflinePayment(20L,
                actionRequest(2L, null, 0L, 0L, "门店已收现金"), 99L, "平台管理员");

        assertEquals(1L, context.getPayStatus());
        verify(orderLogHelper).logSync(anyString(), anyInt(), anyLong(), anyString(), anyString());
    }

    @Test
    void rejectsOmgOrderFromOfflinePaymentPath() {
        PosOrder order = pickupOrder("2", 0L);
        when(posOrderService.getById(20L)).thenReturn(order);

        assertThrows(ServiceException.class, () -> service.confirmOfflinePayment(20L,
                actionRequest(2L, null, 0L, 0L, "不能伪造在线支付"), 99L, "平台管理员"));
        verify(posOrderService, never()).update(any(PosOrder.class), any(Wrapper.class));
    }

    @Test
    void offlineRefundSynchronizesAllBusinessStates() {
        PosOrder order = pickupOrder("1", 1L);
        order.setState(1L);
        when(posOrderService.getById(20L)).thenReturn(order);
        doAnswer(invocation -> {
            PosOrder update = invocation.getArgument(0);
            order.setState(update.getState());
            order.setPayStatus(update.getPayStatus());
            order.setAfterSaleStatus(update.getAfterSaleStatus());
            return true;
        }).when(posOrderService).update(any(PosOrder.class), any(Wrapper.class));

        AdminOrderStatusContext context = service.confirmOfflineRefund(20L,
                actionRequest(1L, null, 1L, 0L, "已现场退还现金"), 99L, "平台管理员");

        assertEquals(4L, context.getState());
        assertEquals(2L, context.getPayStatus());
        assertEquals(3L, context.getAfterSaleStatus());
    }

    @Test
    void completedOrderCannotBeRefunded() {
        PosOrder order = pickupOrder("1", 1L);
        order.setState(3L);
        when(posOrderService.getById(20L)).thenReturn(order);

        assertThrows(ServiceException.class, () -> service.confirmOfflineRefund(20L,
                actionRequest(3L, null, 1L, 0L, "申请退款"), 99L, "平台管理员"));
        verify(posOrderService, never()).update(any(PosOrder.class), any(Wrapper.class));
    }

    @Test
    void blankReasonIsRejectedBeforeAnyWrite() {
        PosOrder order = pickupOrder("1", 0L);
        when(posOrderService.getById(20L)).thenReturn(order);

        assertThrows(ServiceException.class, () -> service.confirmOfflinePayment(20L,
                actionRequest(2L, null, 0L, 0L, "   "), 99L, "平台管理员"));

        verify(posOrderService, never()).update(any(PosOrder.class), any(Wrapper.class));
    }

    @Test
    void riderDeliveryCompletesCashOrderAndPreservesInvariant() {
        PosOrder order = deliveryOrder();
        order.setPayType("1");
        order.setCollectPayment("1");
        order.setPayStatus(0L);
        when(posOrderService.getById(10L)).thenReturn(order);
        doAnswer(invocation -> {
            PosOrder update = invocation.getArgument(0);
            order.setState(update.getState());
            order.setDeliveryStatus(update.getDeliveryStatus());
            order.setPayStatus(update.getPayStatus());
            order.setSdTime(update.getSdTime());
            return true;
        }).when(posOrderService).update(any(PosOrder.class), any(Wrapper.class));

        AdminOrderStatusContext context = service.completeDeliveryByRider(10L, 88L, "骑手甲");

        assertEquals(3L, context.getState());
        assertEquals(3L, context.getDeliveryStatus());
        assertEquals(1L, context.getPayStatus());
        verify(billingService).setSanghuBilling(order);
        verify(billingService).setQishouBilling(order);
    }

    @Test
    void lineRefundCompletesAfterSaleAndExistingRefundSideEffects() {
        PosOrder order = pickupOrder("3", 2L);
        order.setState(1L);
        when(posOrderService.getById(20L)).thenReturn(order);
        doAnswer(invocation -> {
            PosOrder update = invocation.getArgument(0);
            order.setState(update.getState());
            order.setPayStatus(update.getPayStatus());
            order.setAfterSaleStatus(update.getAfterSaleStatus());
            return true;
        }).when(posOrderService).update(any(PosOrder.class), any(Wrapper.class));

        AdminOrderStatusContext context = service.finalizeSystemLineRefund(20L);

        assertEquals(4L, context.getState());
        assertEquals(2L, context.getPayStatus());
        assertEquals(3L, context.getAfterSaleStatus());
        verify(orderLogHelper).logSync(anyString(), anyInt(), any(), anyString(), anyString());
    }

    private PosOrder deliveryOrder() {
        PosOrder order = new PosOrder();
        order.setId(10L);
        order.setDdId("202608100001");
        order.setType(0L);
        order.setPayType("2");
        order.setState(2L);
        order.setDeliveryStatus(2L);
        order.setPayStatus(1L);
        order.setAfterSaleStatus(0L);
        order.setQsId(88L);
        order.setShId(66L);
        order.setMdId(77L);
        order.setAmount(100);
        order.setFood("[]");
        order.setFreight(10D);
        return order;
    }

    private PosOrder pickupOrder(String payType, Long payStatus) {
        PosOrder order = new PosOrder();
        order.setId(20L);
        order.setDdId("202608100002");
        order.setType(1L);
        order.setPayType(payType);
        order.setState(2L);
        order.setPayStatus(payStatus);
        order.setAfterSaleStatus(0L);
        return order;
    }

    private AdminOrderStatusUpdateRequest statusRequest(Long state, Long delivery, Long pay, Long afterSale) {
        AdminOrderStatusUpdateRequest request = new AdminOrderStatusUpdateRequest();
        request.setExpectedState(state);
        request.setExpectedDeliveryStatus(delivery);
        request.setExpectedPayStatus(pay);
        request.setExpectedAfterSaleStatus(afterSale);
        return request;
    }

    private AdminOrderActionRequest actionRequest(Long state, Long delivery, Long pay, Long afterSale,
                                                  String reason) {
        AdminOrderActionRequest request = new AdminOrderActionRequest();
        request.setExpectedState(state);
        request.setExpectedDeliveryStatus(delivery);
        request.setExpectedPayStatus(pay);
        request.setExpectedAfterSaleStatus(afterSale);
        request.setReason(reason);
        return request;
    }
}
