package com.ruoyi.system.service.impl;

import com.ruoyi.system.domain.PosOrderLinePayment;
import com.ruoyi.system.mapper.PosOrderLinePaymentMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.Date;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class PosOrderLinePaymentServiceImplTest {

    @Mock
    private PosOrderLinePaymentMapper paymentMapper;

    @InjectMocks
    private PosOrderLinePaymentServiceImpl paymentService;

    @Test
    void newRequestingAttemptOwnsTheOrderActiveKey() {
        when(paymentMapper.insert(any(PosOrderLinePayment.class))).thenAnswer(invocation -> {
            PosOrderLinePayment row = invocation.getArgument(0);
            row.setId(70L);
            return 1;
        });

        PosOrderLinePayment row = paymentService.createRequesting(
                "DD-100", "LP-100-01", 5L, 9L, 280, "TWD", new Date(1_000_000L));

        assertEquals(70L, row.getId());
        assertEquals("REQUESTING", row.getStatus());
        assertEquals("DD-100", row.getActiveDdId());
        assertEquals(0L, row.getVersion());
        assertEquals(0, row.getReconcileCount());
    }

    @Test
    void newAttemptPersistsExplicitOfflineMode() {
        when(paymentMapper.insert(any(PosOrderLinePayment.class))).thenReturn(1);

        PosOrderLinePayment row = paymentService.createRequesting(
                "DD-OFF-100", "LPOFF-100", 5L, 9L, 280, "TWD",
                "OFFLINE", new Date(1_000_000L));

        assertEquals("OFFLINE", row.getPaymentMode());
        ArgumentCaptor<PosOrderLinePayment> captor =
                ArgumentCaptor.forClass(PosOrderLinePayment.class);
        verify(paymentMapper).insert(captor.capture());
        assertEquals("OFFLINE", captor.getValue().getPaymentMode());
    }

    @Test
    void legacyCreateMethodStillWritesOnlineModeExplicitly() {
        when(paymentMapper.insert(any(PosOrderLinePayment.class))).thenReturn(1);

        PosOrderLinePayment row = paymentService.createRequesting(
                "DD-ONLINE-100", "LP-100", 5L, 9L, 280, "TWD", new Date());

        assertEquals("ONLINE", row.getPaymentMode());
    }

    @Test
    void explicitTerminalStateReleasesActiveKeyUsingCompareAndSet() {
        when(paymentMapper.markTerminal(eq(70L), eq(4L), eq("WAITING_AUTH"),
                eq("FAILED"), any())).thenReturn(1);

        int updated = paymentService.markTerminal(70L, 4L, "WAITING_AUTH", "FAILED");

        assertEquals(1, updated);
        verify(paymentMapper).markTerminal(eq(70L), eq(4L), eq("WAITING_AUTH"),
                eq("FAILED"), any());
    }

    @Test
    void unknownStateNeverReleasesActiveKey() {
        PosOrderLinePayment row = new PosOrderLinePayment();
        row.setActiveDdId("DD-100");
        row.setStatus("CONFIRM_UNKNOWN");

        paymentService.applyActiveKeyInvariant(row);

        assertEquals("DD-100", row.getActiveDdId());
    }

    @Test
    void amountMismatchAndManualReviewNeverReleaseActiveKey() {
        for (String status : java.util.List.of("AMOUNT_MISMATCH", "MANUAL_REVIEW", "PAID")) {
            PosOrderLinePayment row = new PosOrderLinePayment();
            row.setActiveDdId("DD-100");
            row.setStatus(status);

            paymentService.applyActiveKeyInvariant(row);

            assertEquals("DD-100", row.getActiveDdId());
        }
    }

    @Test
    void onlyExplicitFailedOrCancelledAttemptCanHaveNoActiveKey() {
        PosOrderLinePayment row = new PosOrderLinePayment();
        row.setActiveDdId("DD-100");
        row.setStatus("CANCELLED_OR_EXPIRED");

        paymentService.applyActiveKeyInvariant(row);

        assertNull(row.getActiveDdId());
    }
}
