package payment

import (
	"context"
	"encoding/json"
	bizpayment "kra/internal/biz/payment"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/go-pay/gopay/paypal"
)

func TestPayPalCreateAndQueryKeepOrderAndCaptureIDsSeparate(t *testing.T) {
	var createBody map[string]any
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case r.Method == http.MethodPost && r.URL.Path == "/v1/oauth2/token":
			w.Header().Set("Content-Type", "application/json")
			_, _ = w.Write([]byte(`{"access_token":"TOKEN","token_type":"Bearer","expires_in":3600}`))
		case r.Method == http.MethodPost && r.URL.Path == "/v2/checkout/orders":
			if got := r.Header.Get("PayPal-Request-Id"); got != "LOCAL-1" {
				t.Errorf("PayPal-Request-Id = %q, want LOCAL-1", got)
			}
			if err := json.NewDecoder(r.Body).Decode(&createBody); err != nil {
				t.Errorf("decode create body: %v", err)
			}
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusCreated)
			_, _ = w.Write([]byte(`{
				"id":"ORDER-1","status":"CREATED",
				"purchase_units":[{"reference_id":"LOCAL-1","invoice_id":"LOCAL-1","custom_id":"LOCAL-1","amount":{"currency_code":"USD","value":"10.99"}}],
				"links":[{"rel":"approve","href":"https://example.test/approve","method":"GET"}]
			}`))
		case r.Method == http.MethodGet && r.URL.Path == "/v2/checkout/orders/ORDER-1":
			w.Header().Set("Content-Type", "application/json")
			_, _ = w.Write([]byte(`{
				"id":"ORDER-1","status":"COMPLETED",
				"purchase_units":[{
					"reference_id":"LOCAL-1","invoice_id":"LOCAL-1","custom_id":"LOCAL-1",
					"amount":{"currency_code":"USD","value":"10.99"},
					"payments":{"captures":[{"id":"CAPTURE-1","status":"COMPLETED","amount":{"currency_code":"USD","value":"10.99"}}]}
				}]
			}`))
		default:
			http.NotFound(w, r)
		}
	}))
	defer server.Close()

	adapter := &paypalAdapter{}
	config := paypalTestConfig(server.URL)
	created, err := adapter.Create(context.Background(), &bizpayment.PaymentRequest{
		TradeNo:   "LOCAL-1",
		Subject:   "Order one",
		Amount:    1099,
		Currency:  "USD",
		ReturnURL: "https://merchant.test/return",
		Extra:     map[string]any{"payment_source": map[string]any{"paypal": map[string]any{}}},
	}, config)
	if err != nil {
		t.Fatalf("Create() error = %v", err)
	}
	if created.TradeNo != "LOCAL-1" || created.QueryID != "ORDER-1" || created.ProviderTradeNo != "" || created.Status != "pending" {
		t.Fatalf("Create() result = %+v", created)
	}
	units, _ := createBody["purchase_units"].([]any)
	if len(units) != 1 {
		t.Fatalf("purchase_units = %#v", createBody["purchase_units"])
	}
	unit, _ := units[0].(map[string]any)
	amount, _ := unit["amount"].(map[string]any)
	if unit["invoice_id"] != "LOCAL-1" || amount["value"] != "10.99" {
		t.Fatalf("purchase unit = %#v", unit)
	}
	if _, ok := createBody["payment_source"]; !ok {
		t.Fatalf("payment_source was not forwarded: %#v", createBody)
	}

	queried, err := adapter.Query(context.Background(), created.QueryID, config)
	if err != nil {
		t.Fatalf("Query() error = %v", err)
	}
	if queried.Status != "success" || queried.TradeNo != "LOCAL-1" || queried.QueryID != "ORDER-1" || queried.ProviderTradeNo != "CAPTURE-1" || queried.Amount != 1099 || queried.Currency != "USD" {
		t.Fatalf("Query() result = %+v", queried)
	}
}

func TestPayPalCreateRejectsMismatchedResponseIdentityAndAmount(t *testing.T) {
	for _, tc := range []struct {
		name     string
		response string
		want     string
	}{
		{
			name: "merchant identity",
			response: `{
				"id":"ORDER-MISMATCH","status":"CREATED",
				"purchase_units":[{"reference_id":"OTHER","invoice_id":"OTHER","custom_id":"OTHER","amount":{"currency_code":"USD","value":"10.99"}}]
			}`,
			want: "商户订单号不匹配",
		},
		{
			name: "conflicting merchant identities",
			response: `{
				"id":"ORDER-CONFLICT","status":"CREATED",
				"purchase_units":[{"reference_id":"LOCAL-1","invoice_id":"OTHER","amount":{"currency_code":"USD","value":"10.99"}}]
			}`,
			want: "冲突的商户订单号",
		},
		{
			name: "amount",
			response: `{
				"id":"ORDER-AMOUNT","status":"CREATED",
				"purchase_units":[{"reference_id":"LOCAL-1","invoice_id":"LOCAL-1","custom_id":"LOCAL-1","amount":{"currency_code":"USD","value":"11.00"}}]
			}`,
			want: "金额不匹配",
		},
		{
			name: "currency",
			response: `{
				"id":"ORDER-CURRENCY","status":"CREATED",
				"purchase_units":[{"reference_id":"LOCAL-1","invoice_id":"LOCAL-1","custom_id":"LOCAL-1","amount":{"currency_code":"EUR","value":"10.99"}}]
			}`,
			want: "币种不匹配",
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				switch {
				case r.Method == http.MethodPost && r.URL.Path == "/v1/oauth2/token":
					w.Header().Set("Content-Type", "application/json")
					_, _ = w.Write([]byte(`{"access_token":"TOKEN","token_type":"Bearer","expires_in":3600}`))
				case r.Method == http.MethodPost && r.URL.Path == "/v2/checkout/orders":
					w.Header().Set("Content-Type", "application/json")
					w.WriteHeader(http.StatusCreated)
					_, _ = w.Write([]byte(tc.response))
				default:
					http.NotFound(w, r)
				}
			}))
			defer server.Close()

			_, err := (&paypalAdapter{}).Create(context.Background(), &bizpayment.PaymentRequest{
				TradeNo: "LOCAL-1", Subject: "Order one", Amount: 1099, Currency: "USD",
			}, paypalTestConfig(server.URL))
			if err == nil || !strings.Contains(err.Error(), tc.want) {
				t.Fatalf("Create() error = %v, want %q", err, tc.want)
			}
		})
	}
}

func TestPayPalQueryCapturesApprovedOrderBeforeFulfillment(t *testing.T) {
	var captureCalled bool
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case r.Method == http.MethodPost && r.URL.Path == "/v1/oauth2/token":
			w.Header().Set("Content-Type", "application/json")
			_, _ = w.Write([]byte(`{"access_token":"TOKEN","token_type":"Bearer","expires_in":3600}`))
		case r.Method == http.MethodGet && r.URL.Path == "/v2/checkout/orders/ORDER-APPROVED":
			w.Header().Set("Content-Type", "application/json")
			_, _ = w.Write([]byte(`{
				"id":"ORDER-APPROVED","status":"APPROVED",
				"purchase_units":[{"reference_id":"LOCAL-APPROVED","invoice_id":"LOCAL-APPROVED","custom_id":"LOCAL-APPROVED","amount":{"currency_code":"USD","value":"10.99"}}]
			}`))
		case r.Method == http.MethodPost && r.URL.Path == "/v2/checkout/orders/ORDER-APPROVED/capture":
			captureCalled = true
			if got := r.Header.Get("PayPal-Request-Id"); got != "ORDER-APPROVED-capture" {
				t.Errorf("capture PayPal-Request-Id = %q", got)
			}
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusCreated)
			_, _ = w.Write([]byte(`{
				"id":"ORDER-APPROVED","status":"COMPLETED",
				"purchase_units":[{"reference_id":"LOCAL-APPROVED","invoice_id":"LOCAL-APPROVED","custom_id":"LOCAL-APPROVED","amount":{"currency_code":"USD","value":"10.99"},
				"payments":{"captures":[{"id":"CAPTURE-APPROVED","status":"COMPLETED","amount":{"currency_code":"USD","value":"10.99"}}]}}]
			}`))
		default:
			http.NotFound(w, r)
		}
	}))
	defer server.Close()

	result, err := (&paypalAdapter{}).Query(context.Background(), "ORDER-APPROVED", paypalTestConfig(server.URL))
	if err != nil {
		t.Fatalf("Query() error = %v", err)
	}
	if !captureCalled {
		t.Fatal("Query() did not capture approved order")
	}
	if result.Status != "success" || result.TradeNo != "LOCAL-APPROVED" || result.ProviderTradeNo != "CAPTURE-APPROVED" || result.Amount != 1099 || result.Currency != "USD" {
		t.Fatalf("captured result = %+v", result)
	}
}

func TestPayPalOrderDoesNotFulfillPendingCapture(t *testing.T) {
	order := &paypal.OrderDetail{
		Id:     "ORDER-PENDING",
		Status: "COMPLETED",
		PurchaseUnits: []*paypal.PurchaseUnit{{
			InvoiceId: "LOCAL-PENDING",
			Amount:    &paypal.Amount{CurrencyCode: "USD", Value: "10.99"},
			Payments: &paypal.Payments{Captures: []*paypal.Capture{{
				Id: "CAPTURE-PENDING", Status: "PENDING",
				Amount: &paypal.Amount{CurrencyCode: "USD", Value: "10.99"},
			}}},
		}},
	}
	result, err := paypalOrderResult(order, order.Id, "", map[string]any{"amount_scales": map[string]any{"USD": "100"}})
	if err != nil {
		t.Fatal(err)
	}
	if result.Status != "pending" || result.ProviderTradeNo != "" {
		t.Fatalf("pending capture result = %+v", result)
	}
}

func TestPayPalRefundUsesPersistedCaptureID(t *testing.T) {
	var refundBody map[string]any
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case r.Method == http.MethodPost && r.URL.Path == "/v1/oauth2/token":
			w.Header().Set("Content-Type", "application/json")
			_, _ = w.Write([]byte(`{"access_token":"TOKEN","token_type":"Bearer","expires_in":3600}`))
		case r.Method == http.MethodPost && r.URL.Path == "/v2/payments/captures/CAPTURE-1/refund":
			if got := r.Header.Get("PayPal-Request-Id"); got != "REFUND-1" {
				t.Errorf("PayPal-Request-Id = %q, want REFUND-1", got)
			}
			if err := json.NewDecoder(r.Body).Decode(&refundBody); err != nil {
				t.Errorf("decode refund body: %v", err)
			}
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusCreated)
			_, _ = w.Write([]byte(`{"id":"PAYPAL-REFUND-1","invoice_id":"REFUND-1","status":"PENDING","amount":{"currency_code":"USD","value":"1.23"}}`))
		default:
			http.NotFound(w, r)
		}
	}))
	defer server.Close()

	config := paypalTestConfig(server.URL)
	config["provider_trade_no"] = "WRONG-CONFIG-CAPTURE"
	result, err := (&paypalAdapter{}).Refund(context.Background(), &bizpayment.PaymentRefundRequest{
		Provider: bizpayment.PaymentPayPal, TradeNo: "LOCAL-1", ProviderTradeNo: "CAPTURE-1",
		QueryID: "ORDER-1", RefundNo: "REFUND-1", Amount: 123, TotalAmount: 1099, Currency: "USD",
	}, config)
	if err != nil {
		t.Fatalf("Refund() error = %v", err)
	}
	if result.TradeNo != "LOCAL-1" || result.ProviderTradeNo != "PAYPAL-REFUND-1" || result.Amount != 123 || result.Currency != "USD" || result.Status != "pending" {
		t.Fatalf("Refund() result = %+v", result)
	}
	amount, _ := refundBody["amount"].(map[string]any)
	if refundBody["invoice_id"] != "REFUND-1" || amount["value"] != "1.23" {
		t.Fatalf("refund body = %#v", refundBody)
	}
}

func TestPayPalRefundResolvesCaptureFromPersistedQueryID(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case r.Method == http.MethodPost && r.URL.Path == "/v1/oauth2/token":
			w.Header().Set("Content-Type", "application/json")
			_, _ = w.Write([]byte(`{"access_token":"TOKEN","token_type":"Bearer","expires_in":3600}`))
		case r.Method == http.MethodGet && r.URL.Path == "/v2/checkout/orders/ORDER-1":
			w.Header().Set("Content-Type", "application/json")
			_, _ = w.Write([]byte(`{
				"id":"ORDER-1","status":"COMPLETED",
				"purchase_units":[{
					"invoice_id":"LOCAL-1",
					"amount":{"currency_code":"USD","value":"10.99"},
					"payments":{"captures":[{"id":"CAPTURE-FROM-ORDER","status":"COMPLETED"}]}
				}]
			}`))
		case r.Method == http.MethodPost && r.URL.Path == "/v2/payments/captures/CAPTURE-FROM-ORDER/refund":
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusCreated)
			_, _ = w.Write([]byte(`{"id":"PAYPAL-REFUND-2","invoice_id":"REFUND-2","status":"COMPLETED","amount":{"currency_code":"USD","value":"1.23"}}`))
		default:
			http.NotFound(w, r)
		}
	}))
	defer server.Close()

	config := paypalTestConfig(server.URL)
	config["capture_id"] = "WRONG-CONFIG-CAPTURE"
	result, err := (&paypalAdapter{}).Refund(context.Background(), &bizpayment.PaymentRefundRequest{
		Provider: bizpayment.PaymentPayPal, TradeNo: "LOCAL-1", QueryID: "ORDER-1",
		RefundNo: "REFUND-2", Amount: 123, TotalAmount: 1099, Currency: "USD",
	}, config)
	if err != nil {
		t.Fatalf("Refund() error = %v", err)
	}
	if result.ProviderTradeNo != "PAYPAL-REFUND-2" || result.Status != "success" {
		t.Fatalf("Refund() result = %+v", result)
	}
}

func TestPayPalRefundRejectsStaticCaptureIDWithoutDurableIdentity(t *testing.T) {
	config := paypalTestConfig("https://paypal.invalid")
	config["capture_id"] = "CONFIG-CAPTURE"
	_, err := (&paypalAdapter{}).Refund(context.Background(), &bizpayment.PaymentRefundRequest{
		Provider: bizpayment.PaymentPayPal, TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 123, Currency: "USD",
	}, config)
	if err == nil {
		t.Fatal("Refund() accepted a static config capture ID")
	}
}

func TestPayPalRefundResultBindsRefundIdentity(t *testing.T) {
	req := &bizpayment.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 123, Currency: "USD"}
	base := func() *paypal.PaymentCaptureRefund {
		return &paypal.PaymentCaptureRefund{
			Id: "PAYPAL-REFUND-1", InvoiceId: "REFUND-1", Status: "PENDING",
			Amount: &paypal.Amount{CurrencyCode: "USD", Value: "1.23"},
		}
	}

	for _, tc := range []struct {
		name   string
		mutate func(*paypal.PaymentCaptureRefund)
	}{
		{name: "missing invoice id", mutate: func(refund *paypal.PaymentCaptureRefund) { refund.InvoiceId = "" }},
		{name: "mismatched invoice id", mutate: func(refund *paypal.PaymentCaptureRefund) { refund.InvoiceId = "OTHER" }},
		{name: "missing provider refund id", mutate: func(refund *paypal.PaymentCaptureRefund) { refund.Id = "" }},
		{name: "missing amount", mutate: func(refund *paypal.PaymentCaptureRefund) { refund.Amount = nil }},
		{name: "mismatched amount", mutate: func(refund *paypal.PaymentCaptureRefund) { refund.Amount.Value = "1.24" }},
		{name: "mismatched currency", mutate: func(refund *paypal.PaymentCaptureRefund) { refund.Amount.CurrencyCode = "EUR" }},
	} {
		t.Run(tc.name, func(t *testing.T) {
			refund := base()
			tc.mutate(refund)
			if _, err := paypalRefundResult(req, refund, nil); err == nil {
				t.Fatal("paypalRefundResult() accepted an unbound refund response")
			}
		})
	}
}

func TestPayPalCallbackUsesGoPayWebhookVerification(t *testing.T) {
	var verifyBody map[string]any
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case r.Method == http.MethodPost && r.URL.Path == "/v1/oauth2/token":
			w.Header().Set("Content-Type", "application/json")
			_, _ = w.Write([]byte(`{"access_token":"TOKEN","token_type":"Bearer","expires_in":3600}`))
		case r.Method == http.MethodPost && r.URL.Path == "/v1/notifications/verify-webhook-signature":
			if err := json.NewDecoder(r.Body).Decode(&verifyBody); err != nil {
				t.Errorf("decode verify body: %v", err)
			}
			w.Header().Set("Content-Type", "application/json")
			_, _ = w.Write([]byte(`{"verification_status":"SUCCESS"}`))
		default:
			http.NotFound(w, r)
		}
	}))
	defer server.Close()

	body := []byte(`{
		"id":"WEBHOOK-1","event_type":"PAYMENT.CAPTURE.COMPLETED",
		"resource":{
			"id":"CAPTURE-1","status":"COMPLETED","invoice_id":"LOCAL-1",
			"supplementary_data":{"related_ids":{"order_id":"ORDER-1"}}
		}
	}`)
	config := paypalTestConfig(server.URL)
	config["webhook_id"] = "HOOK-1"
	result, err := (&paypalAdapter{}).Callback(context.Background(), &bizpayment.PaymentCallback{
		Body: body,
		Headers: map[string]string{
			"Paypal-Auth-Algo":         "SHA256withRSA",
			"Paypal-Cert-Url":          "https://api.paypal.test/cert",
			"Paypal-Transmission-Id":   "TRANSMISSION-1",
			"Paypal-Transmission-Sig":  "SIGNATURE",
			"Paypal-Transmission-Time": "2026-08-19T00:00:00Z",
		},
	}, config)
	if err != nil {
		t.Fatalf("Callback() error = %v", err)
	}
	if result.TradeNo != "LOCAL-1" || result.QueryID != "ORDER-1" || result.ProviderTradeNo != "CAPTURE-1" || result.EventID != "WEBHOOK-1" || result.Status != "success" {
		t.Fatalf("Callback() result = %+v", result)
	}
	if verifyBody["webhook_id"] != "HOOK-1" || verifyBody["transmission_id"] != "TRANSMISSION-1" {
		t.Fatalf("verify body = %#v", verifyBody)
	}
	if _, ok := verifyBody["webhook_event"].(map[string]any); !ok {
		t.Fatalf("webhook_event was not sent as an object: %#v", verifyBody["webhook_event"])
	}
}

func paypalTestConfig(baseURL string) map[string]any {
	return map[string]any{
		"client_id":     "client-id",
		"client_secret": "client-secret",
		"environment":   "sandbox",
		"api_base_url":  baseURL,
		"cancel_url":    "https://merchant.test/cancel",
		"notify_url":    "https://merchant.test/paypal/webhook",
	}
}
