package com.ruoyi.app.utils.linepay;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.LinkedHashMap;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

class LinePayClientTest {

    private RecordingTransport transport;
    private LinePayClient client;

    @BeforeEach
    void setUp() {
        transport = new RecordingTransport();
        LinePayProperties properties = new LinePayProperties();
        properties.setBaseUrl("https://sandbox-api-pay.line.me");
        properties.setConfirmUrl("https://api.example.com/pay/line/confirm");
        properties.setCancelUrl("https://api.example.com/pay/line/cancel");
        properties.setAppPackageName("com.twanmsdyh.app");
        client = new LinePayClient(properties, new LinePaySigner(), transport, () -> "nonce-123");
    }

    @Test
    void requestBuildsBalancedPackageAndClientRedirectContract() throws Exception {
        transport.responseBody = "{\"returnCode\":\"0000\",\"returnMessage\":\"Success\","
                + "\"info\":{\"transactionId\":\"2026081200000000001\","
                + "\"paymentUrl\":{\"web\":\"https://pay.example/web\"}}}";

        LinePayResponse response = client.request(credential(),
                new LinePayRequest("LP-001", "DD-001", "Order DD-001", 280, "TWD"));

        assertEquals("POST", transport.method);
        assertEquals("https://sandbox-api-pay.line.me/v4/payments/request", transport.url);
        JSONObject body = JSON.parseObject(transport.body);
        assertEquals(280, body.getIntValue("amount"));
        assertEquals(280, body.getJSONArray("packages").getJSONObject(0).getIntValue("amount"));
        assertEquals(280, body.getJSONArray("packages").getJSONObject(0)
                .getJSONArray("products").getJSONObject(0).getIntValue("price"));
        assertFalse(body.getJSONObject("redirectUrls").containsKey("confirmUrlType"));
        assertFalse(body.getJSONObject("redirectUrls").containsKey("appPackageName"));
        assertFalse(body.getJSONObject("options").getJSONObject("payment").containsKey("payType"));
        assertTrue(body.getJSONObject("options").getJSONObject("payment").getBooleanValue("capture"));
        assertEquals("2026081200000000001", response.transactionId());
    }

    @Test
    void retrieveSignsTheSameEncodedQueryThatTransportSends() throws Exception {
        transport.responseBody = "{\"returnCode\":\"1150\",\"returnMessage\":\"No transaction\"}";

        client.retrieveByOrderId(credential(), "LP 订单-1");

        assertEquals("GET", transport.method);
        assertEquals("https://sandbox-api-pay.line.me/v4/payments?orderId=LP+%E8%AE%A2%E5%8D%95-1", transport.url);
        assertEquals("Row7cngEXZA5F5yEJ722+SIXpyJMVOBsff71BZz62oU=",
                transport.headers.get("X-LINE-Authorization"));
    }

    @Test
    void fullRefundUsesEmptyJsonAndNeverSendsRefundAmount() throws Exception {
        transport.responseBody = "{\"returnCode\":\"0000\",\"returnMessage\":\"Success\","
                + "\"info\":{\"refundTransactionId\":\"9001\"}}";

        client.refundFull(credential(), "2026081200000000001");

        assertEquals("POST", transport.method);
        assertEquals("{}", transport.body);
        assertFalse(transport.body.contains("refundAmount"));
        assertEquals(20_000, transport.readTimeoutMs);
    }

    @Test
    void confirmUsesAtLeastFortySecondReadTimeout() throws Exception {
        transport.responseBody = "{\"returnCode\":\"0000\",\"returnMessage\":\"Success\"}";

        client.confirm(credential(), "2026081200000000001", 280, "TWD");

        assertEquals(40_000, transport.readTimeoutMs);
    }

    @Test
    void checkUsesRequestTransactionIdInPath() throws Exception {
        transport.responseBody = "{\"returnCode\":\"0000\"}";

        client.check(credential(), "2026081200000000001");

        assertEquals("https://sandbox-api-pay.line.me/v4/payments/requests/"
                + "2026081200000000001/check", transport.url);
    }

    @Test
    void offlinePayUsesOneTimeKeyContractWithoutOnlineOrDeviceFields() throws Exception {
        transport.responseBody = "{\"returnCode\":\"1145\",\"returnMessage\":\"Authentication required\"}";

        client.payOffline(credential(), new LinePayOfflineRequest(
                "LPOFF-001", "DD-001", "Order DD-001", 280, "TWD",
                "123456789012345678"));

        assertEquals("POST", transport.method);
        assertEquals("https://sandbox-api-pay.line.me/v4/payments/oneTimeKeys/pay", transport.url);
        assertEquals(40_000, transport.readTimeoutMs);
        JSONObject body = JSON.parseObject(transport.body);
        assertEquals("123456789012345678", body.getString("oneTimeKey"));
        assertEquals(280, body.getIntValue("amount"));
        assertFalse(body.containsKey("redirectUrls"));
        assertFalse(body.containsKey("options"));
        assertFalse(body.containsKey("capture"));
        assertFalse(body.containsKey("void"));
        assertFalse(transport.headers.containsKey("X-LINE-MerchantDeviceProfileId"));
        assertFalse(transport.headers.containsKey("X-LINE-MerchantDeviceType"));
    }

    @Test
    void offlineCheckUsesEncodedPermanentOrderId() throws Exception {
        transport.responseBody = "{\"returnCode\":\"0000\",\"info\":{\"status\":\"AUTH_READY\"}}";

        client.checkOffline(credential(), "LPOFF 订单-1");

        assertEquals("GET", transport.method);
        assertEquals("https://sandbox-api-pay.line.me/v4/payments/orders/"
                + "LPOFF%20%E8%AE%A2%E5%8D%95-1/check", transport.url);
        assertEquals(20_000, transport.readTimeoutMs);
    }

    @Test
    void offlineFullRefundUsesOrderIdAndEmptyJson() throws Exception {
        transport.responseBody = "{\"returnCode\":\"0000\",\"info\":{\"refundTransactionId\":\"RF-1\"}}";

        client.refundOfflineFull(credential(), "LPOFF-001");

        assertEquals("POST", transport.method);
        assertEquals("https://sandbox-api-pay.line.me/v4/payments/orders/LPOFF-001/refund", transport.url);
        assertEquals("{}", transport.body);
        assertEquals(20_000, transport.readTimeoutMs);
        assertFalse(transport.body.contains("refundAmount"));
    }

    @Test
    void offlinePayRejectsMalformedOneTimeKeyBeforeTransport() {
        org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class,
                () -> client.payOffline(credential(), new LinePayOfflineRequest(
                        "LPOFF-001", "DD-001", "Order DD-001", 280, "TWD", "123")));
        assertEquals(null, transport.method);
    }

    @Test
    void offlineRequestStringRepresentationRedactsMyCode() {
        LinePayOfflineRequest request = new LinePayOfflineRequest(
                "LPOFF-001", "DD-001", "Order DD-001", 280, "TWD",
                "123456789012345678");

        assertFalse(request.toString().contains("123456789012345678"));
        assertTrue(request.toString().contains("<redacted>"));
    }

    private static LinePayCredential credential() {
        return new LinePayCredential(5L, "1234567890", "test-secret");
    }

    private static final class RecordingTransport implements LinePayHttpTransport {
        String method;
        String url;
        String body;
        Map<String, String> headers;
        int readTimeoutMs;
        String responseBody;

        @Override
        public LinePayHttpResponse execute(String method, String url, String body,
                                           Map<String, String> headers, int readTimeoutMs) {
            this.method = method;
            this.url = url;
            this.body = body;
            this.headers = new LinkedHashMap<>(headers);
            this.readTimeoutMs = readTimeoutMs;
            return new LinePayHttpResponse(200, responseBody);
        }
    }
}
