package com.ruoyi.app.omgpay;

import com.ruoyi.app.omgpay.dto.OmgNotifyField;
import com.ruoyi.app.omgpay.dto.OmgNotifyRequest;
import com.ruoyi.system.omgpay.domain.OmgPaymentAttempt;
import com.ruoyi.system.omgpay.service.IOmgPaymentAttemptService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

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

class OmgPaymentClientReturnServiceTest {
    private static final String HASH_KEY = "5294y06JbISpM5x9";
    private static final String HASH_IV = "v77hoKGq4kWxNNIS";

    private IOmgPaymentAttemptService attempts;
    private OmgPaymentClientReturnService service;

    @BeforeEach
    void setUp() {
        attempts = mock(IOmgPaymentAttemptService.class);
        OmgPaymentProperties properties = new OmgPaymentProperties();
        properties.setAppReturnUrl("com.twanmsdyh.app://pages/OrderList/paySuccess/paySuccess");
        service = new OmgPaymentClientReturnService(attempts, new OmgCheckMacSigner(),
                new OmgPaymentReturnPageRenderer(properties));
        OmgPaymentAttempt attempt = new OmgPaymentAttempt();
        attempt.setId(7L);
        attempt.setDdId("DD-1&next=evil");
        attempt.setMerchantId("1000031");
        attempt.setMerchantTradeNo("OMG123");
        attempt.setAmount(100);
        attempt.setHashKeySnapshot(HASH_KEY);
        attempt.setHashIvSnapshot(HASH_IV);
        when(attempts.selectByMerchantTradeNo("OMG123")).thenReturn(attempt);
    }

    @Test
    void verifiedPaymentResultReturnsNoStoreHtmlThatOnlyNavigatesToApp() {
        LinkedHashMap<String, String> fields = paymentFields();
        fields.put("FutureExtraField", "signed extra");
        OmgPaymentReturnPage page = service.paymentResult(signedRequest(fields));

        assertEquals(200, page.statusCode());
        assertTrue(page.html().contains(
                "com.twanmsdyh.app://pages/OrderList/paySuccess/paySuccess?ddId=DD-1%26next%3Devil"));
        assertFalse(page.html().contains("source="));
        assertTrue(page.html().contains("正在返回 App"));
        assertTrue(page.html().contains("請稍候，App 開啟後會確認最終付款狀態。"));
        assertTrue(page.html().contains("lang=\"zh-Hant-TW\""));
        assertFalse(page.html().contains("请稍候"));
        assertFalse(page.html().contains(HASH_KEY));
        assertFalse(page.html().contains("CheckMacValue"));
    }

    @Test
    void missingAppReturnConfigurationUsesTaiwanTraditional() {
        OmgPaymentReturnPage page = new OmgPaymentReturnPageRenderer(
                new OmgPaymentProperties()).success("DD-1");

        assertEquals(503, page.statusCode());
        assertTrue(page.html().contains("無法返回 App"));
        assertTrue(page.html().contains("App 返回網址尚未設定"));
        assertFalse(page.html().contains("无法返回 App"));
    }

    @Test
    void verifiedPaymentResultProvidesRedactedBrowserDiagnosticsForIosHandoff() {
        OmgPaymentReturnPage page = service.paymentResult(signedRequest(paymentFields()));

        assertEquals(200, page.statusCode());
        assertAll(
                () -> assertTrue(page.html().contains("[OMG-RETURN]")),
                () -> assertTrue(page.html().contains("page_loaded")),
                () -> assertTrue(page.html().contains("auto_open_start")),
                () -> assertTrue(page.html().contains("auto_open_observation")),
                () -> assertTrue(page.html().contains("manual_open_touch")),
                () -> assertTrue(page.html().contains("manual_open_click")),
                () -> assertTrue(page.html().contains("visibilitychange")),
                () -> assertTrue(page.html().contains("pagehide")),
                () -> assertTrue(page.html().contains("pageshow")),
                () -> assertTrue(page.html().contains("unhandledrejection")),
                () -> assertTrue(page.html().contains("targetSummary")),
                () -> assertTrue(page.html().contains("orderRef")),
                () -> assertTrue(page.html().contains("JSON.stringify(snapshot(details))")),
                () -> assertFalse(page.html().contains("rawForm")),
                () -> assertFalse(page.html().contains(HASH_KEY)),
                () -> assertFalse(page.html().contains(HASH_IV)),
                () -> assertFalse(page.html().contains("CheckMacValue")));
    }

    @Test
    void verifiedPaymentResultUsesUniAppBridgeBeforeKeepingSchemeFallback() {
        OmgPaymentReturnPage page = service.paymentResult(signedRequest(paymentFields()));

        assertEquals(200, page.statusCode());
        assertAll(
                () -> assertTrue(page.html().contains(
                        "<script src=\"/pay/omg/bridge.js\"></script>")),
                () -> assertTrue(page.html().contains(
                        "data-app-route=\"/pages/OrderList/paySuccess/paySuccess?ddId=DD-1%26next%3Devil\"")),
                () -> assertTrue(page.html().contains("UniAppJSBridgeReady")),
                () -> assertTrue(page.html().contains("uni.webView.getEnv")),
                () -> assertTrue(page.html().contains("uni.webView.redirectTo")),
                () -> assertTrue(page.html().contains("plus.webview.currentWebview")),
                () -> assertTrue(page.html().contains("current.parent()")),
                () -> assertTrue(page.html().contains("parent.evalJS")),
                () -> assertTrue(page.html().contains("ios_parent_redirect_start")),
                () -> assertTrue(page.html().contains("bridge_redirect_start")),
                () -> assertTrue(page.html().contains("scheme_fallback_start")),
                () -> assertTrue(page.html().contains("routePath:routePath(appRoute)")),
                () -> assertFalse(page.html().contains("route:appRoute")),
                () -> assertTrue(page.html().contains(
                        "com.twanmsdyh.app://pages/OrderList/paySuccess/paySuccess?ddId=DD-1%26next%3Devil")));
    }

    @Test
    void tamperingAnyExtraFieldRejectsTheWholeClientResult() {
        LinkedHashMap<String, String> fields = paymentFields();
        fields.put("FutureExtraField", "signed extra");
        OmgNotifyRequest signed = signedRequest(fields);
        List<OmgNotifyField> tamperedFields = new ArrayList<>(signed.getFields());
        tamperedFields.replaceAll(field -> "FutureExtraField".equals(field.name())
                ? new OmgNotifyField(field.name(), "tampered") : field);

        OmgPaymentReturnPage page = service.paymentResult(
                new OmgNotifyRequest("127.0.0.1", "tampered", tamperedFields, null));

        assertEquals(400, page.statusCode());
        assertFalse(page.html().contains("ddId="));
        assertTrue(page.html().contains("無法驗證返回資料"));
    }

    private OmgNotifyRequest signedRequest(LinkedHashMap<String, String> fields) {
        fields.put("CheckMacValue", new OmgCheckMacSigner().sign(fields, HASH_KEY, HASH_IV));
        List<OmgNotifyField> result = fields.entrySet().stream()
                .map(entry -> new OmgNotifyField(entry.getKey(), entry.getValue())).toList();
        return new OmgNotifyRequest("127.0.0.1", "fixture", result, null);
    }

    private static LinkedHashMap<String, String> paymentFields() {
        LinkedHashMap<String, String> fields = commonFields("1", "Credit_CreditCard");
        fields.put("PaymentDate", "2026/08/13 16:00:00");
        fields.put("PaymentTypeChargeFee", "0.00");
        fields.put("SimulatePaid", "0");
        return fields;
    }

    private static LinkedHashMap<String, String> commonFields(String rtnCode, String paymentType) {
        LinkedHashMap<String, String> fields = new LinkedHashMap<>();
        fields.put("MerchantID", "1000031");
        fields.put("MerchantTradeNo", "OMG123");
        fields.put("StoreID", "");
        fields.put("RtnCode", rtnCode);
        fields.put("RtnMsg", "Succeeded");
        fields.put("TradeNo", "GW123");
        fields.put("TradeAmt", "100");
        fields.put("PaymentType", paymentType);
        fields.put("TradeDate", "2026/08/13 15:59:00");
        fields.put("CustomField1", "");
        fields.put("CustomField2", "");
        fields.put("CustomField3", "");
        fields.put("CustomField4", "");
        return fields;
    }
}
