package com.ruoyi.app.omgpay;

import com.ruoyi.app.omgpay.dto.OmgCreatePaymentResponse;
import com.ruoyi.app.omgpay.dto.OmgQueryPaymentResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.Map;

import static com.ruoyi.app.omgpay.OmgPaymentErrorCode.ORDER_ALREADY_PAID;
import static com.ruoyi.app.omgpay.OmgPaymentErrorCode.ORDER_NOT_AVAILABLE;
import static com.ruoyi.app.omgpay.OmgPaymentErrorCode.PAYMENT_METHOD_INVALID;
import static com.ruoyi.app.omgpay.OmgPaymentErrorCode.PAYMENT_QUERY_NOT_AVAILABLE;
import static com.ruoyi.app.omgpay.OmgPaymentErrorCode.PAYMENT_RETRY_NOT_AVAILABLE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;

class OmgPaymentRetryServiceTest {
    private OmgPaymentQueryService queryService;
    private OmgPaymentCreateService createService;
    private OmgPaymentRetryService service;

    @BeforeEach
    void setUp() {
        queryService = mock(OmgPaymentQueryService.class);
        createService = mock(OmgPaymentCreateService.class);
        service = new OmgPaymentRetryService(queryService, createService);
    }

    @Test
    void replacesVerifiedUnpaidAttemptWithoutSelectedPaymentType() {
        OmgPaymentCreateOutcome expected = outcome();
        when(queryService.query(5L, "DD-1")).thenReturn(query("UNPAID", "0", null));
        when(createService.replaceActiveForRetry(5L, "DD-1", "OMGOLD", OmgPaymentMethod.CREDIT))
                .thenReturn(expected);

        OmgPaymentCreateOutcome actual = service.retry(5L, "DD-1", OmgPaymentMethod.CREDIT);

        assertSame(expected, actual);
        verify(createService).replaceActiveForRetry(5L, "DD-1", "OMGOLD", OmgPaymentMethod.CREDIT);
        verify(createService, never()).create(5L, "DD-1", OmgPaymentMethod.CREDIT);
    }

    @Test
    void createsFreshAttemptAfterVerifiedGatewayFailureClosedOldAttempt() {
        OmgPaymentCreateOutcome expected = outcome();
        when(queryService.query(5L, "DD-1")).thenReturn(query("FAILED", "10200095", null));
        when(createService.create(5L, "DD-1", OmgPaymentMethod.APPLE_PAY)).thenReturn(expected);

        assertSame(expected, service.retry(5L, "DD-1", OmgPaymentMethod.APPLE_PAY));

        verify(createService).create(5L, "DD-1", OmgPaymentMethod.APPLE_PAY);
        verify(createService, never()).replaceActiveForRetry(5L, "DD-1", "OMGOLD", OmgPaymentMethod.APPLE_PAY);
    }

    @Test
    void rejectsPaidAttemptWithoutCreatingAnotherPayment() {
        when(queryService.query(5L, "DD-1")).thenReturn(query("PAID", "1", "Credit_CreditCard"));

        OmgPaymentBusinessException error = assertThrows(OmgPaymentBusinessException.class,
                () -> service.retry(5L, "DD-1", OmgPaymentMethod.CREDIT));

        assertEquals(ORDER_ALREADY_PAID, error.getCode());
        verifyNoInteractions(createService);
    }

    @Test
    void rejectsUnpaidAttemptThatAlreadyHasPaymentType() {
        when(queryService.query(5L, "DD-1")).thenReturn(query("UNPAID", "0", "Credit_CreditCard"));

        OmgPaymentBusinessException error = assertThrows(OmgPaymentBusinessException.class,
                () -> service.retry(5L, "DD-1", OmgPaymentMethod.CREDIT));

        assertEquals(PAYMENT_RETRY_NOT_AVAILABLE, error.getCode());
        verifyNoInteractions(createService);
    }

    @Test
    void rejectsUnknownGatewayStateWithoutMutatingAttempt() {
        when(queryService.query(5L, "DD-1")).thenReturn(query("UNKNOWN", "7", null));

        OmgPaymentBusinessException error = assertThrows(OmgPaymentBusinessException.class,
                () -> service.retry(5L, "DD-1", OmgPaymentMethod.CREDIT));

        assertEquals(PAYMENT_RETRY_NOT_AVAILABLE, error.getCode());
        verifyNoInteractions(createService);
    }

    @Test
    void createsFreshAttemptWhenNoActiveAttemptRemains() {
        // 旧尝试被自动补偿任务关闭为 FAILED/SUPERSEDED 后 active 指针已释放，
        // query 找不到可查记录时，重新付款必须回落到新建而不是死路。
        OmgPaymentCreateOutcome expected = outcome();
        when(queryService.query(5L, "DD-1"))
                .thenThrow(new OmgPaymentBusinessException(PAYMENT_QUERY_NOT_AVAILABLE, 77L));
        when(createService.create(5L, "DD-1", OmgPaymentMethod.CREDIT)).thenReturn(expected);

        assertSame(expected, service.retry(5L, "DD-1", OmgPaymentMethod.CREDIT));

        verify(createService).create(5L, "DD-1", OmgPaymentMethod.CREDIT);
        verify(createService, never()).replaceActiveForRetry(5L, "DD-1", "OMGOLD", OmgPaymentMethod.CREDIT);
    }

    @Test
    void propagatesUnrelatedQueryErrorsWithoutCreating() {
        when(queryService.query(5L, "DD-1"))
                .thenThrow(new OmgPaymentBusinessException(ORDER_NOT_AVAILABLE));

        OmgPaymentBusinessException error = assertThrows(OmgPaymentBusinessException.class,
                () -> service.retry(5L, "DD-1", OmgPaymentMethod.CREDIT));

        assertEquals(ORDER_NOT_AVAILABLE, error.getCode());
        verifyNoInteractions(createService);
    }

    @Test
    void rejectsNullPaymentMethodBeforeQuery() {
        OmgPaymentBusinessException error = assertThrows(OmgPaymentBusinessException.class,
                () -> service.retry(5L, "DD-1", null));

        assertEquals(PAYMENT_METHOD_INVALID, error.getCode());
        verifyNoInteractions(queryService, createService);
    }

    private static OmgQueryPaymentResponse query(String status, String tradeStatus, String paymentType) {
        return new OmgQueryPaymentResponse(status, tradeStatus, "OMGOLD", "GW1", 100,
                null, "2026/08/14 11:17:01", paymentType, "0", "0");
    }

    private static OmgPaymentCreateOutcome outcome() {
        return new OmgPaymentCreateOutcome(
                new OmgCreatePaymentResponse("CREATED", "https://stage.example", Map.of()),
                9L, "DD-1", 5L, 77L, 100, "OMGNE***0001");
    }
}
