package com.ruoyi.app.omgpay;

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import com.ruoyi.app.omgpay.dto.OmgNotifyField;
import com.ruoyi.app.omgpay.dto.OmgNotifyRequest;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.system.omgpay.service.OmgIpnAuditService;
import org.junit.jupiter.api.Test;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

class OmgPaymentNotifyControllerTest {

    @Test
    void exposesAnonymousDtoOnlyNotifyContract() throws Exception {
        Method notify = OmgPaymentController.class.getDeclaredMethod("notify", OmgNotifyRequest.class);

        assertNotNull(notify.getAnnotation(Anonymous.class));
        assertArrayEquals(new String[]{"/notify"}, notify.getAnnotation(PostMapping.class).value());
        assertNotNull(notify.getParameters()[0].getAnnotation(OmgNotifyForm.class));
        assertTrue(java.util.Arrays.stream(notify.getParameterTypes())
                .noneMatch(Map.class::isAssignableFrom));
        assertTrue(java.util.Arrays.stream(notify.getParameterTypes())
                .noneMatch(type -> type.getName().contains("HttpServletRequest")));
    }

    @Test
    void logsAndAuditsCompleteRawFormBeforeReturningOk() {
        OmgPaymentNotifyService notifyService = mock(OmgPaymentNotifyService.class);
        OmgIpnAuditService audit = mock(OmgIpnAuditService.class);
        OmgPaymentController controller = controller(notifyService, audit);
        String raw = "MerchantTradeNo=FULL123&CustomField1=&CheckMacValue=ABC";
        OmgNotifyRequest request = new OmgNotifyRequest("127.0.0.1", raw,
                List.of(new OmgNotifyField("MerchantTradeNo", "FULL123")), null);
        when(notifyService.process(request)).thenReturn(true);
        ListAppender<ILoggingEvent> logs = captureLogs();

        ResponseEntity<String> response = controller.notify(request);

        assertEquals("1|OK", response.getBody());
        verify(audit).append("127.0.0.1", raw);
        assertTrue(logs.list.stream().anyMatch(event -> event.getFormattedMessage().contains(raw)));
    }

    @Test
    void ipnFailureDoesNotBlockPaymentButTrustOrTransactionFailureReturnsError() {
        OmgPaymentNotifyService notifyService = mock(OmgPaymentNotifyService.class);
        OmgIpnAuditService audit = mock(OmgIpnAuditService.class);
        OmgPaymentController controller = controller(notifyService, audit);
        OmgNotifyRequest request = new OmgNotifyRequest("127.0.0.1", "RtnCode=1", List.of(), null);
        doThrow(new IllegalStateException("audit unavailable")).when(audit).append(anyString(), anyString());
        when(notifyService.process(request)).thenReturn(true, false).thenThrow(new IllegalStateException("db"));

        assertEquals("1|OK", controller.notify(request).getBody());
        assertEquals("0|ERROR", controller.notify(request).getBody());
        assertEquals("0|ERROR", controller.notify(request).getBody());
    }

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

    private static ListAppender<ILoggingEvent> captureLogs() {
        Logger logger = (Logger) LoggerFactory.getLogger(OmgPaymentController.class);
        ListAppender<ILoggingEvent> appender = new ListAppender<>();
        appender.start();
        logger.addAppender(appender);
        return appender;
    }
}
