package payment

import (
	"context"
	"crypto"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"io"
	bizpayment "kra/internal/biz/payment"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"

	"kra/pkg/paymentkit"

	gopayAlipay "github.com/go-pay/gopay/alipay"
	gopayDouyin "github.com/go-pay/gopay/douyin"
	gopayWechat "github.com/go-pay/gopay/wechat"
	gopayWechatV3 "github.com/go-pay/gopay/wechat/v3"
)

func testRSAKeyPair(t *testing.T) (*rsa.PrivateKey, string, string) {
	t.Helper()
	key, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		t.Fatal(err)
	}
	privatePEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
	publicDER, err := x509.MarshalPKIXPublicKey(&key.PublicKey)
	if err != nil {
		t.Fatal(err)
	}
	publicPEM := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: publicDER})
	return key, string(privatePEM), string(publicPEM)
}

func testRSA2Sign(t *testing.T, key *rsa.PrivateKey, data []byte) string {
	t.Helper()
	digest := sha256.Sum256(data)
	signature, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, digest[:])
	if err != nil {
		t.Fatal(err)
	}
	return base64.StdEncoding.EncodeToString(signature)
}

func TestAlipayGoPayRequestParamHonorsGatewayAndVerifiesResponse(t *testing.T) {
	platformKey, privatePEM, publicPEM := testRSAKeyPair(t)
	var requestForm url.Values
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		body, err := io.ReadAll(r.Body)
		if err != nil {
			t.Errorf("read request: %v", err)
			return
		}
		requestForm, err = url.ParseQuery(string(body))
		if err != nil {
			t.Errorf("parse request: %v", err)
			return
		}
		if requestForm.Get("method") != "alipay.trade.query" {
			t.Errorf("method = %q", requestForm.Get("method"))
		}
		if !strings.Contains(requestForm.Get("biz_content"), `"out_trade_no":"T-ALIPAY"`) {
			t.Errorf("biz_content = %q", requestForm.Get("biz_content"))
		}
		ok, err := gopayAlipay.VerifySyncSign(goPayAlipayKey(publicPEM, true), alipaySignContent(requestForm), requestForm.Get("sign"))
		if err != nil || !ok {
			t.Errorf("request signature valid = %v, err = %v", ok, err)
		}
		response := []byte(`{"code":"10000","msg":"Success","trade_no":"P-ALIPAY","out_trade_no":"T-ALIPAY","trade_status":"TRADE_SUCCESS","total_amount":"10.00","buyer_pay_amount":"9.00","point_amount":"1.00","receipt_amount":"9.00","trans_currency":"CNY"}`)
		payload := fmt.Sprintf(`{"alipay_trade_query_response":%s,"sign":%q}`, response, testRSA2Sign(t, platformKey, response))
		w.Header().Set("Content-Type", "application/json")
		_, _ = io.WriteString(w, payload)
	}))
	defer server.Close()

	result, err := (&alipayAdapter{}).Query(context.Background(), "T-ALIPAY", map[string]any{
		"app_id":      "app-test",
		"private_key": privatePEM,
		"public_key":  publicPEM,
		"gateway_url": server.URL,
	})
	if err != nil {
		t.Fatal(err)
	}
	if requestForm == nil || result.TradeNo != "T-ALIPAY" || result.ProviderTradeNo != "P-ALIPAY" || result.Amount != 1000 || result.Currency != "CNY" {
		t.Fatalf("unexpected result: %+v, request: %v", result, requestForm)
	}
	if !result.AmountBreakdownKnown || result.PayerPaidAmount != 900 || result.CashPaidAmount != 800 || result.PointPaidAmount != 100 || result.DiscountAmount != 100 || result.SettlementAmount != 900 {
		t.Fatalf("unexpected amount breakdown: %+v", result)
	}
}

func TestAlipayTradePayUsesGoPaySDK(t *testing.T) {
	platformKey, privatePEM, publicPEM := testRSAKeyPair(t)
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		body, err := io.ReadAll(r.Body)
		if err != nil {
			t.Errorf("read request: %v", err)
			return
		}
		form, err := url.ParseQuery(string(body))
		if err != nil {
			t.Errorf("parse request: %v", err)
			return
		}
		if form.Get("method") != "alipay.trade.pay" || !strings.Contains(form.Get("biz_content"), `"auth_code":"BARCODE-1"`) {
			t.Errorf("request form = %v", form)
		}
		response := []byte(`{"code":"10000","msg":"Success","trade_no":"P-ALIPAY-PAY","out_trade_no":"T-ALIPAY-PAY","total_amount":"10.00","buyer_pay_amount":"10.00"}`)
		payload := fmt.Sprintf(`{"alipay_trade_pay_response":%s,"sign":%q}`, response, testRSA2Sign(t, platformKey, response))
		w.Header().Set("Content-Type", "application/json")
		_, _ = io.WriteString(w, payload)
	}))
	defer server.Close()

	result, err := (&alipayAdapter{}).Create(context.Background(), &bizpayment.PaymentRequest{
		Provider: bizpayment.PaymentAlipay, TradeNo: "T-ALIPAY-PAY", Subject: "subject", Amount: 1000,
		Currency: "CNY", Extra: map[string]any{"method": "barcode", "auth_code": "BARCODE-1"},
	}, map[string]any{
		"app_id": "app-test", "private_key": privatePEM, "public_key": publicPEM, "gateway_url": server.URL,
	})
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "success" || result.TradeNo != "T-ALIPAY-PAY" || result.ProviderTradeNo != "P-ALIPAY-PAY" || result.QueryID != "" {
		t.Fatalf("result = %+v", result)
	}
}

func TestAlipayGoPayCallbackVerificationDoesNotMutateFields(t *testing.T) {
	key, _, publicPEM := testRSAKeyPair(t)
	fields := map[string]string{
		"app_id":       "app-test",
		"trade_no":     "P-ALIPAY",
		"out_trade_no": "T-ALIPAY",
		"trade_status": "TRADE_SUCCESS",
		"sign_type":    "RSA2",
	}
	body := toGoPayBodyMap(fields)
	body.Remove("sign_type")
	sign, err := gopayAlipay.GetRsaSign(body, gopayAlipay.RSA2, key)
	if err != nil {
		t.Fatal(err)
	}
	fields["sign"] = sign
	config := map[string]any{"app_id": "app-test", "public_key": publicPEM}
	if err = verifyAlipay(fields, config); err != nil {
		t.Fatal(err)
	}
	if fields["sign"] == "" || fields["sign_type"] != "RSA2" {
		t.Fatalf("verification mutated source fields: %#v", fields)
	}
	fields["trade_no"] = "tampered"
	if err = verifyAlipay(fields, config); err == nil {
		t.Fatal("tampered callback unexpectedly verified")
	}
}

func TestWechatV2GoPayCallbackVerificationAndSandboxAmount(t *testing.T) {
	const key = "wechat-secret"
	var requestValues map[string]string
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		body, err := io.ReadAll(r.Body)
		if err != nil {
			t.Errorf("read request: %v", err)
			return
		}
		requestValues, err = paymentutil.XMLValues(body)
		if err != nil {
			t.Errorf("parse request: %v", err)
			return
		}
		if requestValues["total_fee"] != "123" {
			t.Errorf("total_fee = %q, want 123", requestValues["total_fee"])
		}
		if err = verifyWechatV2(requestValues, key); err != nil {
			t.Errorf("request signature: %v", err)
		}
		response := map[string]string{"return_code": "SUCCESS", "return_msg": "OK", "result_code": "SUCCESS", "prepay_id": "prepay"}
		response["sign"] = wechatV2Sign(response, key, "MD5")
		_, _ = w.Write(paymentutil.XMLEncode(response))
	}))
	defer server.Close()

	result, err := (&wechatV2Adapter{}).Create(context.Background(), &bizpayment.PaymentRequest{
		TradeNo: "T-WECHAT", Subject: "subject", Amount: 123, Currency: "CNY", NotifyURL: "http://notify.local",
	}, map[string]any{
		"app_id": "wx-test", "merchant_id": "mch-test", "mch_key": key,
		"environment": "sandbox", "create_url": server.URL,
	})
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "created" || requestValues["total_fee"] != "123" {
		t.Fatalf("unexpected create result: %+v, request: %#v", result, requestValues)
	}

	callbackValues := map[string]string{
		"return_code": "SUCCESS", "result_code": "SUCCESS", "appid": "wx-test", "mch_id": "mch-test",
		"out_trade_no": "T-WECHAT", "transaction_id": "P-WECHAT", "trade_state": "SUCCESS",
	}
	callbackValues["sign"] = wechatV2Sign(callbackValues, key, "MD5")
	originalSign := callbackValues["sign"]
	if err = verifyWechatV2(callbackValues, key); err != nil {
		t.Fatal(err)
	}
	if callbackValues["sign"] != originalSign {
		t.Fatal("verification mutated source values")
	}
	callbackBody := paymentutil.XMLEncode(callbackValues)
	callbackResult, err := (&wechatV2Adapter{}).Callback(context.Background(), &bizpayment.PaymentCallback{Body: callbackBody}, map[string]any{
		"app_id": "wx-test", "merchant_id": "mch-test", "mch_key": key,
	})
	if err != nil {
		t.Fatal(err)
	}
	if callbackResult.Status != "success" || callbackResult.ProviderTradeNo != "P-WECHAT" {
		t.Fatalf("unexpected callback result: %+v", callbackResult)
	}
}

func TestWechatV2MicropayUsesGoPaySDKAndValidatesIdentities(t *testing.T) {
	const key = "wechat-secret"
	var requestValues map[string]string
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/pay/micropay" {
			t.Errorf("request path = %q, want /pay/micropay", r.URL.Path)
		}
		body, err := io.ReadAll(r.Body)
		if err != nil {
			t.Errorf("read request: %v", err)
			return
		}
		requestValues, err = paymentutil.XMLValues(body)
		if err != nil {
			t.Errorf("parse request: %v", err)
			return
		}
		if requestValues["auth_code"] != "AUTH-CODE-1" || requestValues["out_trade_no"] != "T-WECHAT-MICRO" {
			t.Errorf("micropay request identity = %#v", requestValues)
		}
		if requestValues["trade_type"] != "" || requestValues["notify_url"] != "" {
			t.Errorf("micropay request contains unified-order fields: %#v", requestValues)
		}
		if err = verifyWechatV2(requestValues, key); err != nil {
			t.Errorf("request signature: %v", err)
		}
		response := map[string]string{
			"return_code": "SUCCESS", "return_msg": "OK", "result_code": "SUCCESS",
			"appid": "wx-test", "mch_id": "mch-test", "out_trade_no": "T-WECHAT-MICRO",
			"transaction_id": "WX-TRANSACTION-1", "total_fee": "123", "cash_fee": "123", "fee_type": "CNY",
		}
		response["sign"] = wechatV2Sign(response, key, "MD5")
		_, _ = w.Write(paymentutil.XMLEncode(response))
	}))
	defer server.Close()

	result, err := (&wechatV2Adapter{}).Create(context.Background(), &bizpayment.PaymentRequest{
		Provider: bizpayment.PaymentWechatV2, TradeNo: "T-WECHAT-MICRO", Subject: "subject", Amount: 123,
		Currency: "CNY", NotifyURL: "http://notify.local",
		Extra: map[string]any{"method": "barcode", "auth_code": "AUTH-CODE-1"},
	}, map[string]any{
		"app_id": "wx-test", "merchant_id": "mch-test", "mch_key": key,
		"create_url": server.URL + "/pay/micropay",
	})
	if err != nil {
		t.Fatal(err)
	}
	if requestValues == nil || result.Status != "success" || result.TradeNo != "T-WECHAT-MICRO" || result.ProviderTradeNo != "WX-TRANSACTION-1" || result.QueryID != "" {
		t.Fatalf("unexpected micropay result: %+v, request: %#v", result, requestValues)
	}
	if result.Amount != 123 || result.Currency != "CNY" {
		t.Fatalf("unexpected micropay amount: %+v", result)
	}
}

func TestWechatV2MicropayRejectsMissingAuthCodeAndUnknownMode(t *testing.T) {
	_, err := (&wechatV2Adapter{}).Create(context.Background(), &bizpayment.PaymentRequest{
		Provider: bizpayment.PaymentWechatV2, TradeNo: "T-WECHAT-MICRO-MISSING", Subject: "subject", Amount: 1,
		Currency: "CNY", NotifyURL: "http://notify.local", Extra: map[string]any{"method": "micropay"},
	}, map[string]any{"app_id": "wx-test", "merchant_id": "mch-test", "mch_key": "wechat-secret"})
	if err == nil || !strings.Contains(err.Error(), "auth_code") {
		t.Fatalf("missing auth_code error = %v", err)
	}
	if _, err = wechatV2CreateMethod(map[string]any{"method": "unsupported"}, nil); err == nil {
		t.Fatal("unknown v2 mode unexpectedly accepted")
	}
}

func TestWechatV2CreateMethodMapsSupportedModes(t *testing.T) {
	for _, tc := range []struct {
		name   string
		extra  map[string]any
		config map[string]any
		want   string
	}{
		{name: "default", want: gopayWechat.TradeType_JsApi},
		{name: "mini program", extra: map[string]any{"method": "mini-program"}, want: gopayWechat.TradeType_JsApi},
		{name: "app", extra: map[string]any{"trade_type": "APP"}, want: gopayWechat.TradeType_App},
		{name: "native", extra: map[string]any{"method": "qrcode"}, want: gopayWechat.TradeType_Native},
		{name: "h5", extra: map[string]any{"pay_method": "h5"}, want: gopayWechat.TradeType_H5},
		{name: "micropay", extra: map[string]any{"method": "barcode"}, want: "MICROPAY"},
		{name: "config fallback", config: map[string]any{"trade_type": "micropay"}, want: "MICROPAY"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			got, err := wechatV2CreateMethod(tc.extra, tc.config)
			if err != nil || got != tc.want {
				t.Fatalf("mode = %q, err = %v, want %q", got, err, tc.want)
			}
		})
	}
}

func TestWechatV2MicropayRejectsInvalidSuccessResponse(t *testing.T) {
	const key = "wechat-secret"
	for _, tc := range []struct {
		name   string
		mutate func(map[string]string)
		want   string
	}{
		{name: "missing merchant order", mutate: func(values map[string]string) { delete(values, "out_trade_no") }, want: "out_trade_no"},
		{name: "mismatched merchant order", mutate: func(values map[string]string) { values["out_trade_no"] = "OTHER" }, want: "不匹配"},
		{name: "missing transaction", mutate: func(values map[string]string) { delete(values, "transaction_id") }, want: "transaction_id"},
		{name: "mismatched amount", mutate: func(values map[string]string) { values["total_fee"] = "124" }, want: "金额不匹配"},
		{name: "mismatched currency", mutate: func(values map[string]string) { values["fee_type"] = "USD" }, want: "币种不匹配"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
				response := map[string]string{
					"return_code": "SUCCESS", "result_code": "SUCCESS", "out_trade_no": "T-WECHAT-MICRO",
					"transaction_id": "WX-TRANSACTION-1", "total_fee": "123", "fee_type": "CNY",
				}
				tc.mutate(response)
				response["sign"] = wechatV2Sign(response, key, "MD5")
				_, _ = w.Write(paymentutil.XMLEncode(response))
			}))
			defer server.Close()

			_, err := (&wechatV2Adapter{}).Create(context.Background(), &bizpayment.PaymentRequest{
				Provider: bizpayment.PaymentWechatV2, TradeNo: "T-WECHAT-MICRO", Subject: "subject", Amount: 123,
				Currency: "CNY", Extra: map[string]any{"method": "micropay", "auth_code": "AUTH-CODE-1"},
			}, map[string]any{
				"app_id": "wx-test", "merchant_id": "mch-test", "mch_key": key,
				"create_url": server.URL + "/pay/micropay",
			})
			if err == nil || !strings.Contains(err.Error(), tc.want) {
				t.Fatalf("invalid response error = %v, want %q", err, tc.want)
			}
		})
	}
}

func TestWechatV2RejectsUnsignedProviderResponse(t *testing.T) {
	if err := verifyWechatV2Response(map[string]string{
		"return_code": "SUCCESS",
		"result_code": "SUCCESS",
	}, "wechat-secret", "MD5"); err == nil || !strings.Contains(err.Error(), "缺少签名") {
		t.Fatalf("unsigned response error = %v", err)
	}
}

type roundTripperFunc func(*http.Request) (*http.Response, error)

func (f roundTripperFunc) RoundTrip(request *http.Request) (*http.Response, error) {
	return f(request)
}

func TestWechatV3EndpointTransportPreservesSignedPathAndQuery(t *testing.T) {
	endpoint, err := url.Parse("https://payments.test.local")
	if err != nil {
		t.Fatal(err)
	}
	request, err := http.NewRequest(http.MethodGet, "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/T-1?mchid=M-1", nil)
	if err != nil {
		t.Fatal(err)
	}
	request.Header.Set("Authorization", `WECHATPAY2-SHA256-RSA2048 mchid="M-1"`)

	transport := &wechatV3EndpointTransport{
		endpoint: endpoint,
		base: roundTripperFunc(func(got *http.Request) (*http.Response, error) {
			if got.URL.Scheme != "https" || got.URL.Host != "payments.test.local" {
				t.Fatalf("endpoint = %s", got.URL)
			}
			if got.URL.EscapedPath() != "/v3/pay/transactions/out-trade-no/T-1" || got.URL.RawQuery != "mchid=M-1" {
				t.Fatalf("signed target changed to %s", got.URL.RequestURI())
			}
			if got.Header.Get("Authorization") != request.Header.Get("Authorization") {
				t.Fatal("authorization header changed while rewriting endpoint")
			}
			return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody, Header: make(http.Header), Request: got}, nil
		}),
	}

	if _, err = transport.RoundTrip(request); err != nil {
		t.Fatal(err)
	}
	if request.URL.Host != "api.mch.weixin.qq.com" {
		t.Fatal("transport mutated the original request")
	}
}

func TestWechatV3PrepayResultUsesGoPayClientSigning(t *testing.T) {
	_, privatePEM, _ := testRSAKeyPair(t)
	client, err := gopayWechatV3.NewClientV3(
		"mch-test",
		"merchant-serial",
		"01234567890123456789012345678901",
		privatePEM,
	)
	if err != nil {
		t.Fatal(err)
	}

	for _, tc := range []struct {
		name      string
		tradeType string
		assert    func(*testing.T, map[string]any)
	}{
		{
			name: "jsapi", tradeType: "jsapi",
			assert: func(t *testing.T, payload map[string]any) {
				if payload["appId"] != "wx-test" || payload["package"] != "prepay_id=PREPAY-1" || strings.TrimSpace(fmt.Sprint(payload["paySign"])) == "" {
					t.Fatalf("JSAPI payload = %#v", payload)
				}
			},
		},
		{
			name: "applet", tradeType: "mini_program",
			assert: func(t *testing.T, payload map[string]any) {
				if payload["appId"] != "wx-test" || payload["package"] != "prepay_id=PREPAY-1" || strings.TrimSpace(fmt.Sprint(payload["paySign"])) == "" {
					t.Fatalf("applet payload = %#v", payload)
				}
			},
		},
		{
			name: "app", tradeType: "app",
			assert: func(t *testing.T, payload map[string]any) {
				if payload["appid"] != "wx-test" || payload["partnerid"] != "mch-test" || payload["prepayid"] != "PREPAY-1" || strings.TrimSpace(fmt.Sprint(payload["sign"])) == "" {
					t.Fatalf("App payload = %#v", payload)
				}
			},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			result, err := wechatV3PrepayCreateResult(client, "wx-test", tc.tradeType, "T-WECHAT-V3", &gopayWechatV3.PrepayRsp{
				Code:     gopayWechatV3.Success,
				Response: &gopayWechatV3.Prepay{PrepayId: "PREPAY-1"},
			})
			if err != nil {
				t.Fatal(err)
			}
			if result.TradeNo != "T-WECHAT-V3" || result.QueryID != "" || result.Status != "created" {
				t.Fatalf("result = %+v", result)
			}
			var payload map[string]any
			if err = json.Unmarshal(result.Payload, &payload); err != nil {
				t.Fatal(err)
			}
			tc.assert(t, payload)
		})
	}
}

func TestWechatV3CodePayResultKeepsPlatformIdentityAndBreakdown(t *testing.T) {
	result, err := wechatV3CodePayResult("T-WECHAT-CODE", &gopayWechatV3.CodePayRsp{
		Code: gopayWechatV3.Success,
		Response: &gopayWechatV3.CodePay{
			OutTradeNo:    "T-WECHAT-CODE",
			TransactionId: "WX-CODE-1",
			TradeState:    gopayWechatV3.TradeStateSuccess,
			Amount:        &gopayWechatV3.Amount{Total: 100, PayerTotal: 90, Currency: "CNY"},
		},
	})
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "success" || result.TradeNo != "T-WECHAT-CODE" || result.ProviderTradeNo != "WX-CODE-1" || result.QueryID != "" {
		t.Fatalf("result = %+v", result)
	}
	if result.Amount != 100 || result.PayerPaidAmount != 90 || result.DiscountAmount != 10 || !result.AmountBreakdownKnown {
		t.Fatalf("amount breakdown = %+v", result)
	}
}

func TestWechatV3CodePayResultRejectsMismatchedOrder(t *testing.T) {
	_, err := wechatV3CodePayResult("T-WECHAT-CODE", &gopayWechatV3.CodePayRsp{
		Code:     gopayWechatV3.Success,
		Response: &gopayWechatV3.CodePay{OutTradeNo: "OTHER", TransactionId: "WX-CODE-1", TradeState: gopayWechatV3.TradeStateSuccess, Amount: &gopayWechatV3.Amount{Total: 1, Currency: "CNY"}},
	})
	if err == nil || !strings.Contains(err.Error(), "商户订单号") {
		t.Fatalf("mismatched order error = %v", err)
	}
}

func TestDouyinPrepayResultUsesGoPayClientSigning(t *testing.T) {
	_, privatePEM, _ := testRSAKeyPair(t)
	client, err := gopayDouyin.NewClient("mch-test", "merchant-serial", "01234567890123456789012345678901", privatePEM)
	if err != nil {
		t.Fatal(err)
	}
	for _, app := range []bool{false, true} {
		result, err := douyinPrepayResult(client, "douyin-app", "T-DOUYIN", "PREPAY-1", app)
		if err != nil {
			t.Fatal(err)
		}
		var payload map[string]any
		if err = json.Unmarshal(result.Payload, &payload); err != nil {
			t.Fatal(err)
		}
		if result.Provider != bizpayment.PaymentDouyin || result.TradeNo != "T-DOUYIN" || strings.TrimSpace(fmt.Sprint(payload["sign"])) == "" && strings.TrimSpace(fmt.Sprint(payload["paySign"])) == "" {
			t.Fatalf("app=%v result=%+v payload=%#v", app, result, payload)
		}
		if app {
			if payload["appid"] != "douyin-app" || payload["prepayid"] != "PREPAY-1" {
				t.Fatalf("app payload = %#v", payload)
			}
		} else if payload["appId"] != "douyin-app" || payload["package"] != "prepay_id=PREPAY-1" {
			t.Fatalf("JSAPI payload = %#v", payload)
		}
	}
}

func TestDouyinCreateMethodRejectsUnknownMode(t *testing.T) {
	if got, err := douyinCreateMethod(map[string]any{"method": "unsupported"}, nil); err == nil || got != "" {
		t.Fatalf("unknown mode = %q, err = %v", got, err)
	}
	for _, tc := range []struct {
		value string
		want  string
	}{
		{value: "app_pay", want: "app"},
		{value: "h5", want: "h5"},
		{value: "qrcode", want: "native"},
		{value: "jsapi", want: "jsapi"},
	} {
		got, err := douyinCreateMethod(map[string]any{"method": tc.value}, nil)
		if err != nil || got != tc.want {
			t.Fatalf("mode %q = %q, err = %v", tc.value, got, err)
		}
	}
}

func TestWechatV3GoPayCallbackVerificationAndDecryption(t *testing.T) {
	platformKey, merchantPrivatePEM, platformPublicPEM := testRSAKeyPair(t)
	const (
		apiV3Key      = "01234567890123456789012345678901"
		resourceNonce = "0123456789ab"
		headerNonce   = "callback-nonce"
		timestamp     = "1787068800"
		serial        = "ABCDEF123456"
	)

	plain := []byte(`{"appid":"wx-test","mchid":"mch-test","out_trade_no":"T-WECHAT-V3","transaction_id":"P-WECHAT-V3","trade_state":"SUCCESS"}`)
	block, err := aes.NewCipher([]byte(apiV3Key))
	if err != nil {
		t.Fatal(err)
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		t.Fatal(err)
	}
	ciphertext := base64.StdEncoding.EncodeToString(gcm.Seal(nil, []byte(resourceNonce), plain, []byte("transaction")))
	body, err := json.Marshal(map[string]any{
		"id": "EVENT-WECHAT-V3", "event_type": "TRANSACTION.SUCCESS", "resource_type": "encrypt-resource", "summary": "payment success",
		"resource": map[string]any{"algorithm": "AEAD_AES_256_GCM", "ciphertext": ciphertext, "associated_data": "transaction", "original_type": "transaction", "nonce": resourceNonce},
	})
	if err != nil {
		t.Fatal(err)
	}
	signature := testRSA2Sign(t, platformKey, []byte(timestamp+"\n"+headerNonce+"\n"+string(body)+"\n"))
	callback := &bizpayment.PaymentCallback{
		Body: body,
		Headers: map[string]string{
			gopayWechatV3.HeaderTimestamp: timestamp,
			gopayWechatV3.HeaderNonce:     headerNonce,
			gopayWechatV3.HeaderSerial:    serial,
			gopayWechatV3.HeaderSignature: signature,
		},
	}

	result, err := (&wechatV3Adapter{}).Callback(context.Background(), callback, map[string]any{
		"api_v3_key": apiV3Key, "platform_cert": platformPublicPEM, "private_key": merchantPrivatePEM,
		"platform_serial_no": strings.ToLower(serial), "serial_no": "merchant-serial", "app_id": "wx-test", "merchant_id": "mch-test",
	})
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "success" || result.TradeNo != "T-WECHAT-V3" || result.ProviderTradeNo != "P-WECHAT-V3" {
		t.Fatalf("unexpected callback result: %+v", result)
	}
	if callback.Query["event_id"] != "EVENT-WECHAT-V3" {
		t.Fatalf("event id = %q", callback.Query["event_id"])
	}
}
