package com.ruoyi.app.omgpay;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.ArgumentCaptor;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.time.Clock;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

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.anyMap;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class OmgPaymentFormFactoryTest {
    @Test
    void buildsSignedCreditCardForm() {
        OmgPaymentProperties properties = properties();
        OmgCheckMacSigner signer = mock(OmgCheckMacSigner.class);
        String checkMacValue = "A".repeat(64);
        when(signer.sign(anyMap(), eq("KEY"), eq("IV"))).thenReturn(checkMacValue);
        OmgPaymentFormFactory factory = new OmgPaymentFormFactory(properties, signer,
                Clock.fixed(Instant.parse("2026-08-13T07:30:23Z"), ZoneOffset.UTC));

        OmgPaymentForm credit = factory.create("DD-<script>#|訂單", 100, "1000031", "KEY", "IV",
                "OMG12345678901234567", OmgPaymentMethod.CREDIT);

        assertEquals(OmgPaymentFormFactory.STAGE_GATEWAY_URL, credit.gatewayUrl());
        assertEquals(Set.of("MerchantID", "MerchantTradeNo", "MerchantTradeDate", "PaymentType",
                "TotalAmount", "TradeDesc", "ItemName", "ReturnURL", "OrderResultURL", "ChoosePayment",
                "UnionPay", "EncryptType", "InvoiceMark", "NeedExtraPaidInfo", "CheckMacValue"),
                credit.fields().keySet());
        assertEquals("2026/08/13 15:30:23", credit.fields().get("MerchantTradeDate"));
        assertEquals("Credit", credit.fields().get("ChoosePayment"));
        assertEquals("2", credit.fields().get("UnionPay"));
        assertFalse(credit.fields().containsKey("IgnorePayment"));
        assertEquals("Foodie order DDscript", credit.fields().get("TradeDesc"));
        assertFalse(credit.fields().get("ItemName").contains("|"));
        assertTrue(credit.fields().get("ItemName").length() <= 120);
        assertEquals("Y", credit.fields().get("NeedExtraPaidInfo"));
        assertEquals("https://foodieapi.waimai-paotui.com/pay/omg/result",
                credit.fields().get("OrderResultURL"));
        assertEquals(checkMacValue, credit.fields().get("CheckMacValue"));
        ArgumentCaptor<Map<String, String>> signedFields = ArgumentCaptor.forClass(Map.class);
        verify(signer).sign(signedFields.capture(), eq("KEY"), eq("IV"));
        assertEquals(14, signedFields.getValue().size());
        Map<String, String> expectedSignedFields = new LinkedHashMap<>(credit.fields());
        expectedSignedFields.remove("CheckMacValue");
        assertEquals(expectedSignedFields, signedFields.getValue());
        assertThrows(UnsupportedOperationException.class, () -> credit.fields().put("extra", "value"));
    }

    @Test
    void buildsSignedApplePayForm() {
        OmgPaymentProperties properties = properties();
        OmgCheckMacSigner signer = mock(OmgCheckMacSigner.class);
        String checkMacValue = "A".repeat(64);
        when(signer.sign(anyMap(), eq("KEY"), eq("IV"))).thenReturn(checkMacValue);
        OmgPaymentFormFactory factory = new OmgPaymentFormFactory(properties, signer,
                Clock.fixed(Instant.parse("2026-08-13T07:30:23Z"), ZoneOffset.UTC));

        OmgPaymentForm applePay = factory.create("DD-1", 100, "1000031", "KEY", "IV",
                "OMG12345678901234567", OmgPaymentMethod.APPLE_PAY);

        assertEquals(Set.of("MerchantID", "MerchantTradeNo", "MerchantTradeDate", "PaymentType",
                "TotalAmount", "TradeDesc", "ItemName", "ReturnURL", "OrderResultURL", "ChoosePayment",
                "EncryptType", "InvoiceMark", "NeedExtraPaidInfo", "CheckMacValue"),
                applePay.fields().keySet());
        assertEquals("ApplePay", applePay.fields().get("ChoosePayment"));
        assertFalse(applePay.fields().containsKey("UnionPay"));
        assertFalse(applePay.fields().containsKey("IgnorePayment"));
        assertEquals(checkMacValue, applePay.fields().get("CheckMacValue"));
        ArgumentCaptor<Map<String, String>> signedFields = ArgumentCaptor.forClass(Map.class);
        verify(signer).sign(signedFields.capture(), eq("KEY"), eq("IV"));
        assertEquals(13, signedFields.getValue().size());
        Map<String, String> expectedSignedFields = new LinkedHashMap<>(applePay.fields());
        expectedSignedFields.remove("CheckMacValue");
        assertEquals(expectedSignedFields, signedFields.getValue());
    }

    @ParameterizedTest
    @ValueSource(strings = {"http://example.com/pay/omg/notify", "https://example.com/wrong",
            "https://example.com/pay/omg/notify?q=1", "https://example.com/pay/omg/notify#x",
            "https://user@example.com/pay/omg/notify", "/pay/omg/notify"})
    void rejectsUnsafeReturnUrls(String returnUrl) {
        OmgPaymentProperties properties = properties();
        properties.setReturnUrl(returnUrl);
        assertThrows(IllegalArgumentException.class, properties::requireSafeReturnUrl);
    }

    @Test
    void rejectsUnsafeResultAndAppReturnUrls() {
        OmgPaymentProperties properties = properties();
        properties.setOrderResultUrl("https://example.com/wrong");
        assertThrows(IllegalArgumentException.class, properties::requireSafeOrderResultUrl);
        properties.setOrderResultUrl("https://foodieapi.waimai-paotui.com/pay/omg/result");
        properties.setAppReturnUrl("com.twanmsdyh.app://pages/Other/page");
        assertThrows(IllegalArgumentException.class, properties::safeAppReturnUrlOrNull);
    }

    @Test
    void springContextCreatesPaymentFormFactoryWithDependencies() {
        try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
            context.registerBean(OmgPaymentProperties.class, OmgPaymentFormFactoryTest::properties);
            context.registerBean(OmgCheckMacSigner.class);
            context.register(OmgPaymentFormFactory.class);

            context.refresh();

            assertNotNull(context.getBean(OmgPaymentFormFactory.class));
        }
    }

    private static OmgPaymentProperties properties() {
        OmgPaymentProperties properties = new OmgPaymentProperties();
        properties.setReturnUrl("https://foodieapi.waimai-paotui.com/pay/omg/notify");
        properties.setOrderResultUrl("https://foodieapi.waimai-paotui.com/pay/omg/result");
        properties.setAppReturnUrl("com.twanmsdyh.app://pages/OrderList/paySuccess/paySuccess");
        return properties;
    }
}
