package com.ruoyi.app.omgpay;

import com.ruoyi.app.omgpay.dto.OmgCreatePaymentResponse;
import com.ruoyi.app.omgpay.dto.OmgPaymentErrorResponse;
import com.ruoyi.app.omgpay.dto.OmgRetryPaymentRequest;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.system.omgpay.service.OmgIpnAuditService;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;

class OmgPaymentRetryControllerTest {
    @Test
    void exposesAuthenticatedRetryContractAndReturnsNewForm() throws Exception {
        Method method = OmgPaymentController.class.getDeclaredMethod(
                "retry", String.class, OmgRetryPaymentRequest.class);
        assertArrayEquals(new String[]{"/retry"}, method.getAnnotation(PostMapping.class).value());
        assertEquals("token", method.getParameters()[0].getAnnotation(RequestHeader.class).name());
        assertNotNull(method.getParameters()[1].getAnnotation(RequestBody.class));
        assertEquals(Set.of("orderId", "paymentMethod"),
                java.util.Arrays.stream(OmgRetryPaymentRequest.class.getDeclaredFields())
                        .map(Field::getName).collect(Collectors.toSet()));

        OmgPaymentTokenUserResolver resolver = mock(OmgPaymentTokenUserResolver.class);
        OmgPaymentRetryService retryService = mock(OmgPaymentRetryService.class);
        OmgCreatePaymentResponse response = new OmgCreatePaymentResponse(
                "CREATED", "https://stage.example", Map.of("MerchantTradeNo", "OMGNEW"));
        when(resolver.requireUserId("token")).thenReturn(5L);
        when(retryService.retry(5L, "DD-1", OmgPaymentMethod.APPLE_PAY)).thenReturn(new OmgPaymentCreateOutcome(
                response, 9L, "DD-1", 5L, 77L, 100, "OMGNE***0001"));
        OmgRetryPaymentRequest request = new OmgRetryPaymentRequest();
        request.setOrderId("DD-1");
        request.setPaymentMethod("APPLE_PAY");

        AjaxResult result;
        try (MockedStatic<MessageUtils> messages = mockStatic(MessageUtils.class)) {
            messages.when(() -> MessageUtils.message(anyString())).thenAnswer(call -> call.getArgument(0));
            result = controller(resolver, retryService).retry("token", request);
        }

        assertSame(response, result.get("data"));
        org.mockito.Mockito.verify(retryService).retry(5L, "DD-1", OmgPaymentMethod.APPLE_PAY);
    }

    @Test
    void mapsRetryBusinessAndUnexpectedFailuresToStableStatus() {
        try (MockedStatic<MessageUtils> messages = mockStatic(MessageUtils.class)) {
            messages.when(() -> MessageUtils.message(anyString())).thenAnswer(call -> call.getArgument(0));
            OmgPaymentTokenUserResolver resolver = mock(OmgPaymentTokenUserResolver.class);
            OmgPaymentRetryService retryService = mock(OmgPaymentRetryService.class);
            when(resolver.requireUserId("token")).thenReturn(5L);
            when(retryService.retry(5L, "DD-1", OmgPaymentMethod.CREDIT)).thenThrow(
                    new OmgPaymentBusinessException(OmgPaymentErrorCode.PAYMENT_RETRY_NOT_AVAILABLE));
            OmgRetryPaymentRequest request = new OmgRetryPaymentRequest();
            request.setOrderId("DD-1");
            request.setPaymentMethod("CREDIT");
            OmgPaymentController controller = controller(resolver, retryService);

            AjaxResult business = controller.retry("token", request);
            assertEquals("PAYMENT_RETRY_NOT_AVAILABLE",
                    ((OmgPaymentErrorResponse) business.get("data")).status());

            org.mockito.Mockito.doThrow(new IllegalStateException("db"))
                    .when(retryService).retry(5L, "DD-1", OmgPaymentMethod.CREDIT);
            AjaxResult unexpected = controller.retry("token", request);
            assertEquals("PAYMENT_RETRY_FAILED",
                    ((OmgPaymentErrorResponse) unexpected.get("data")).status());
        }
    }

    @Test
    void rejectsInvalidPaymentMethodsWithoutCallingRetryService() {
        try (MockedStatic<MessageUtils> messages = mockStatic(MessageUtils.class)) {
            messages.when(() -> MessageUtils.message(anyString())).thenAnswer(call -> call.getArgument(0));
            OmgPaymentTokenUserResolver resolver = mock(OmgPaymentTokenUserResolver.class);
            OmgPaymentRetryService retryService = mock(OmgPaymentRetryService.class);
            when(resolver.requireUserId("token")).thenReturn(5L);
            OmgPaymentController controller = controller(resolver, retryService);

            for (String paymentMethod : new String[]{null, "ALL", "Credit", " CREDIT "}) {
                OmgRetryPaymentRequest request = new OmgRetryPaymentRequest();
                request.setOrderId("DD-1");
                request.setPaymentMethod(paymentMethod);

                AjaxResult result = controller.retry("token", request);

                assertEquals("PAYMENT_METHOD_INVALID", ((OmgPaymentErrorResponse) result.get("data")).status());
            }
            org.mockito.Mockito.verifyNoInteractions(retryService);
        }
    }

    private static OmgPaymentController controller(OmgPaymentTokenUserResolver resolver,
                                                    OmgPaymentRetryService retryService) {
        return new OmgPaymentController(resolver, mock(OmgPaymentCreateService.class),
                mock(OmgPaymentNotifyService.class), mock(OmgIpnAuditService.class),
                mock(OmgPaymentQueryService.class), retryService, mock(OmgPaymentRefundService.class));
    }
}
