package payment

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/json"
	"encoding/pem"
	"io"
	bizpayment "kra/internal/biz/payment"
	"math/big"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"
	"time"

	gopayAlipay "github.com/go-pay/gopay/alipay"
	gopayAlipayV3 "github.com/go-pay/gopay/alipay/v3"
)

func testAlipayV3Config(t *testing.T) (*rsa.PrivateKey, map[string]any) {
	t.Helper()
	key, privatePEM, _ := testRSAKeyPair(t)
	template := &x509.Certificate{
		SerialNumber:          big.NewInt(1001),
		Subject:               pkixName("GoPay Alipay V3 Test"),
		Issuer:                pkixName("GoPay Alipay V3 Test"),
		NotBefore:             time.Now().Add(-time.Hour),
		NotAfter:              time.Now().Add(time.Hour),
		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		BasicConstraintsValid: true,
		IsCA:                  true,
	}
	der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
	if err != nil {
		t.Fatal(err)
	}
	certificate := string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}))
	return key, map[string]any{
		"app_id":      "app-v3-test",
		"private_key": privatePEM,
		"app_cert":    certificate,
		"root_cert":   certificate,
		"public_cert": certificate,
		"environment": "sandbox",
	}
}

func pkixName(commonName string) pkix.Name {
	return pkix.Name{CommonName: commonName, Organization: []string{"GoPay Tests"}}
}

func cloneAlipayV3Config(values map[string]any) map[string]any {
	clone := make(map[string]any, len(values)+1)
	for key, value := range values {
		clone[key] = value
	}
	return clone
}

func alipayV3ResponseSignature(t *testing.T, key *rsa.PrivateKey, body string) (string, string, string) {
	t.Helper()
	timestamp := "1660000000000"
	nonce := "alipay-v3-test-nonce"
	signature := testRSA2Sign(t, key, []byte(timestamp+"\n"+nonce+"\n"+body+"\n"))
	return timestamp, nonce, signature
}

func writeAlipayV3Response(w http.ResponseWriter, timestamp, nonce, signature, body string) {
	w.Header().Set("Content-Type", "application/json")
	w.Header().Set(gopayAlipayV3.HeaderTimestamp, timestamp)
	w.Header().Set(gopayAlipayV3.HeaderNonce, nonce)
	w.Header().Set(gopayAlipayV3.HeaderSignature, signature)
	w.WriteHeader(http.StatusOK)
	_, _ = io.WriteString(w, body)
}

func TestAlipayV3CreateRESTMethodsUseGoPay(t *testing.T) {
	platformKey, baseConfig := testAlipayV3Config(t)
	for _, tc := range []struct {
		name       string
		method     string
		path       string
		response   string
		extra      map[string]any
		wantStatus string
		wantTrade  string
	}{
		{
			name: "trade create", path: "/v3/alipay/trade/create",
			response: `{"trade_no":"P-V3-CREATE","out_trade_no":"T-V3-CREATE"}`,
			extra:    map[string]any{"buyer_open_id": "buyer-open-id"}, wantStatus: "created", wantTrade: "P-V3-CREATE",
		},
		{
			name: "trade pay", method: "barcode", path: "/v3/alipay/trade/pay",
			response: `{"trade_no":"P-V3-PAY","out_trade_no":"T-V3-PAY","total_amount":"10.00","buyer_pay_amount":"9.00","point_amount":"1.00","receipt_amount":"9.00"}`,
			extra:    map[string]any{"auth_code": "BARCODE-V3"}, wantStatus: "success", wantTrade: "P-V3-PAY",
		},
		{
			name: "precreate", method: "native", path: "/v3/alipay/trade/precreate",
			response:   `{"out_trade_no":"T-V3-PRECREATE","qr_code":"https://qr.example/v3"}`,
			wantStatus: "created",
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			tradeNo := "T-V3-CREATE"
			if tc.method == "barcode" {
				tradeNo = "T-V3-PAY"
			} else if tc.method == "native" {
				tradeNo = "T-V3-PRECREATE"
			}
			timestamp, nonce, signature := alipayV3ResponseSignature(t, platformKey, tc.response)
			var requestBody map[string]any
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				if r.URL.Path != tc.path {
					t.Errorf("request path = %q, want %q", r.URL.Path, tc.path)
				}
				if !strings.HasPrefix(r.Header.Get(gopayAlipayV3.HeaderAuthorization), gopayAlipayV3.SignTypeRSA+" ") {
					t.Errorf("authorization = %q", r.Header.Get(gopayAlipayV3.HeaderAuthorization))
				}
				if err := json.NewDecoder(r.Body).Decode(&requestBody); err != nil {
					t.Errorf("decode request: %v", err)
				}
				writeAlipayV3Response(w, timestamp, nonce, signature, tc.response)
			}))
			defer server.Close()

			config := cloneAlipayV3Config(baseConfig)
			config["api_base_url"] = server.URL
			extra := cloneAlipayV3Config(tc.extra)
			if tc.method != "" {
				extra["method"] = tc.method
			}
			result, err := (&alipayV3Adapter{}).Create(context.Background(), &bizpayment.PaymentRequest{
				Provider: bizpayment.PaymentAlipayV3, TradeNo: tradeNo, Subject: "subject", Amount: 1000,
				Currency: "CNY", NotifyURL: "https://merchant.example/alipay-v3/notify", Extra: extra,
			}, config)
			if err != nil {
				t.Fatal(err)
			}
			if requestBody == nil || requestBody["out_trade_no"] != tradeNo || requestBody["notify_url"] == "" {
				t.Fatalf("request body = %#v", requestBody)
			}
			if result.Provider != bizpayment.PaymentAlipayV3 || result.Status != tc.wantStatus || result.TradeNo != tradeNo || result.ProviderTradeNo != tc.wantTrade {
				t.Fatalf("result = %+v", result)
			}
			if tc.method == "barcode" {
				if requestBody["scene"] != "bar_code" || requestBody["auth_code"] != "BARCODE-V3" {
					t.Fatalf("trade pay request = %#v", requestBody)
				}
				if !result.AmountBreakdownKnown || result.Amount != 1000 || result.PayerPaidAmount != 900 || result.PointPaidAmount != 100 {
					t.Fatalf("trade pay amount breakdown = %+v", result)
				}
			}
			if tc.method == "" && (requestBody["product_code"] != "JSAPI_PAY" || requestBody["op_app_id"] != "app-v3-test") {
				t.Fatalf("trade create defaults = %#v", requestBody)
			}
		})
	}
}

func TestAlipayV3GeneratedPaymentMethodsUseGoPay(t *testing.T) {
	_, config := testAlipayV3Config(t)
	config["gateway_url"] = "https://merchant-gateway.example/gateway.do"
	for _, method := range []string{"app", "page", "wap"} {
		t.Run(method, func(t *testing.T) {
			result, err := (&alipayV3Adapter{}).Create(context.Background(), &bizpayment.PaymentRequest{
				Provider: bizpayment.PaymentAlipayV3, TradeNo: "T-V3-" + strings.ToUpper(method), Subject: "subject",
				Amount: 1000, Currency: "CNY", NotifyURL: "https://merchant.example/alipay-v3/notify",
				ReturnURL: "https://merchant.example/alipay-v3/return", Extra: map[string]any{"method": method},
			}, config)
			if err != nil {
				t.Fatal(err)
			}
			var payload map[string]string
			if err = json.Unmarshal(result.Payload, &payload); err != nil {
				t.Fatal(err)
			}
			generated := payload["order_string"]
			if generated == "" {
				generated = payload["pay_url"]
			}
			if !strings.Contains(generated, "alipay.trade."+method+".pay") || !strings.Contains(generated, "notify_url=") {
				t.Fatalf("generated payment data = %q", generated)
			}
			if method != "app" && !strings.HasPrefix(generated, "https://merchant-gateway.example/gateway.do?") {
				t.Fatalf("rewritten gateway URL = %q", generated)
			}
		})
	}
}

func TestAlipayV3QueryVerifiesSignedIdentityAndAmount(t *testing.T) {
	platformKey, config := testAlipayV3Config(t)
	body := `{"trade_no":"P-V3-QUERY","out_trade_no":"T-V3-QUERY","trade_status":"TRADE_SUCCESS","total_amount":"10.00","buyer_pay_amount":"9.00","point_amount":"1.00","receipt_amount":"9.00"}`
	timestamp, nonce, signature := alipayV3ResponseSignature(t, platformKey, body)
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/v3/alipay/trade/query" {
			t.Errorf("request path = %q", r.URL.Path)
		}
		writeAlipayV3Response(w, timestamp, nonce, signature, body)
	}))
	defer server.Close()
	config["api_base_url"] = server.URL

	result, err := (&alipayV3Adapter{}).Query(context.Background(), "T-V3-QUERY", config)
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "success" || result.TradeNo != "T-V3-QUERY" || result.ProviderTradeNo != "P-V3-QUERY" || result.Amount != 1000 || result.Currency != "CNY" {
		t.Fatalf("result = %+v", result)
	}
	if !result.AmountBreakdownKnown || result.PayerPaidAmount != 900 || result.CashPaidAmount != 800 || result.PointPaidAmount != 100 || result.DiscountAmount != 100 || result.SettlementAmount != 900 {
		t.Fatalf("amount breakdown = %+v", result)
	}
}

func TestAlipayV3QueryRejectsInvalidSignatureAndIdentity(t *testing.T) {
	platformKey, baseConfig := testAlipayV3Config(t)
	for _, tc := range []struct {
		name      string
		body      string
		signature string
		want      string
	}{
		{name: "mismatched order", body: `{"trade_no":"P-V3","out_trade_no":"OTHER","trade_status":"TRADE_SUCCESS","total_amount":"10.00"}`, want: "out_trade_no"},
		{name: "invalid signature", body: `{"trade_no":"P-V3","out_trade_no":"T-V3-QUERY","trade_status":"TRADE_SUCCESS","total_amount":"10.00"}`, signature: "invalid", want: "signature"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			timestamp, nonce, signature := alipayV3ResponseSignature(t, platformKey, tc.body)
			if tc.signature != "" {
				signature = tc.signature
			}
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
				writeAlipayV3Response(w, timestamp, nonce, signature, tc.body)
			}))
			defer server.Close()
			config := cloneAlipayV3Config(baseConfig)
			config["api_base_url"] = server.URL
			_, err := (&alipayV3Adapter{}).Query(context.Background(), "T-V3-QUERY", config)
			if err == nil || !strings.Contains(strings.ToLower(err.Error()), strings.ToLower(tc.want)) {
				t.Fatalf("error = %v, want %q", err, tc.want)
			}
		})
	}
}

func TestAlipayV3RefundValidatesResponse(t *testing.T) {
	platformKey, baseConfig := testAlipayV3Config(t)
	for _, tc := range []struct {
		name      string
		refundFee string
		wantError bool
	}{
		{name: "success", refundFee: "1.23"},
		{name: "amount mismatch", refundFee: "1.22", wantError: true},
	} {
		t.Run(tc.name, func(t *testing.T) {
			body := `{"trade_no":"P-V3-REFUND","out_trade_no":"T-V3-REFUND","fund_change":"Y","refund_fee":"` + tc.refundFee + `"}`
			timestamp, nonce, signature := alipayV3ResponseSignature(t, platformKey, body)
			var requestBody map[string]any
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				if err := json.NewDecoder(r.Body).Decode(&requestBody); err != nil {
					t.Errorf("decode request: %v", err)
				}
				writeAlipayV3Response(w, timestamp, nonce, signature, body)
			}))
			defer server.Close()
			config := cloneAlipayV3Config(baseConfig)
			config["api_base_url"] = server.URL
			result, err := (&alipayV3Adapter{}).Refund(context.Background(), &bizpayment.PaymentRefundRequest{
				Provider: bizpayment.PaymentAlipayV3, TradeNo: "T-V3-REFUND", ProviderTradeNo: "P-V3-REFUND",
				RefundNo: "R-V3-REFUND", Amount: 123, Currency: "CNY",
			}, config)
			if tc.wantError {
				if err == nil || !strings.Contains(err.Error(), "金额不匹配") {
					t.Fatalf("error = %v", err)
				}
				return
			}
			if err != nil {
				t.Fatal(err)
			}
			if requestBody["out_request_no"] != "R-V3-REFUND" || requestBody["refund_amount"] != "1.23" {
				t.Fatalf("refund request = %#v", requestBody)
			}
			if result.Amount != 123 || result.ProviderTradeNo != "P-V3-REFUND" || result.Status != "created" {
				t.Fatalf("refund result = %+v", result)
			}
		})
	}
}

func TestAlipayV3CallbackUsesGoPayCertificateVerification(t *testing.T) {
	platformKey, config := testAlipayV3Config(t)
	fields := map[string]string{
		"app_id":       "app-v3-test",
		"trade_no":     "P-V3-CALLBACK",
		"out_trade_no": "T-V3-CALLBACK",
		"trade_status": "TRADE_SUCCESS",
		"sign_type":    "RSA2",
	}
	bodyMap := toGoPayBodyMap(fields)
	bodyMap.Remove("sign_type")
	signature, err := gopayAlipay.GetRsaSign(bodyMap, gopayAlipay.RSA2, platformKey)
	if err != nil {
		t.Fatal(err)
	}
	fields["sign"] = signature
	values := url.Values{}
	for key, value := range fields {
		values.Set(key, value)
	}
	callback := &bizpayment.PaymentCallback{
		Body: []byte(values.Encode()), Headers: map[string]string{"Content-Type": "application/x-www-form-urlencoded"},
	}
	result, err := (&alipayV3Adapter{}).Callback(context.Background(), callback, config)
	if err != nil {
		t.Fatal(err)
	}
	if result.Provider != bizpayment.PaymentAlipayV3 || result.Status != "success" || result.TradeNo != "T-V3-CALLBACK" || result.ProviderTradeNo != "P-V3-CALLBACK" {
		t.Fatalf("callback result = %+v", result)
	}
	callback.Body = []byte(strings.ReplaceAll(values.Encode(), "P-V3-CALLBACK", "P-V3-TAMPERED"))
	if _, err = (&alipayV3Adapter{}).Callback(context.Background(), callback, config); err == nil {
		t.Fatal("tampered callback unexpectedly verified")
	}
}
