package lakala

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"hfb_sys/backend/internal/timeutil"
)

func TestVerifyNotifyDetail(t *testing.T) {
	key, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		t.Fatalf("generate key: %v", err)
	}
	privatePEM, certPEM := testKeyPairPEM(t, key)
	client := NewClient(Config{
		GatewayURL: "https://example.com",
		AppID:      "app-1",
		SerialNo:   "serial-1",
		MerchantID: "merchant-1",
		PrivateKey: privatePEM,
		NotifyCert: certPEM,
	})

	body := `{"req_data":{"out_order_no":"NO1","total_amount":100,"trade_state":"SUCCESS"}}`
	message := "1710000000\nabc123\n" + body + "\n"
	signature, err := signRSA(key, []byte(message))
	if err != nil {
		t.Fatalf("signRSA: %v", err)
	}
	authorization := `LKLAPI-SHA256withRSA timestamp="1710000000",nonce_str="abc123",signature="` + signature + `"`

	result := client.VerifyNotifyDetail(body, authorization)
	if !result.OK {
		t.Fatalf("VerifyNotifyDetail().OK = false, got=%s expected=%v base=%v", result.Got, result.Expected, result.BaseString)
	}

	tampered := client.VerifyNotifyDetail(strings.Replace(body, "100", "101", 1), authorization)
	if tampered.OK {
		t.Fatal("VerifyNotifyDetail() = true after body changed, want false")
	}
}

func TestParsePayloadNormalizesAliases(t *testing.T) {
	params, err := ParsePayload([]byte(`{
		"req_data": {
			"out_order_no": "ORDER1",
			"pay_order_no": "PAY1",
			"total_amount": 88,
			"trade_state": "SUCCESS",
			"trade_time": "20240521101010"
		}
	}`))
	if err != nil {
		t.Fatalf("ParsePayload error = %v", err)
	}
	if params["third_order_id"] != "ORDER1" {
		t.Fatalf("third_order_id = %q, want ORDER1", params["third_order_id"])
	}
	if params["provider_order_id"] != "PAY1" {
		t.Fatalf("provider_order_id = %q, want PAY1", params["provider_order_id"])
	}
	if params["amount"] != "88" {
		t.Fatalf("amount = %q, want 88", params["amount"])
	}
	if params["status"] != "paid" {
		t.Fatalf("status = %q, want paid", params["status"])
	}
}

func TestNormalizeRefundStatusTreatsSuccessCodeAsRefunded(t *testing.T) {
	status := normalizeRefundStatus(map[string]string{
		"code": "BBS00000",
		"msg":  "成功",
	})
	if status != "refunded" {
		t.Fatalf("normalizeRefundStatus() = %q, want refunded", status)
	}
}

func TestNormalizeRefundStatusTreatsSuccessCodeWithRefundingAsRefunded(t *testing.T) {
	status := normalizeRefundStatus(map[string]string{
		"code":          "BBS00000",
		"msg":           "成功",
		"status":        "refunding",
		"refund_amount": "960",
		"trade_no":      "20260610110110001231120132051564",
	})
	if status != "refunded" {
		t.Fatalf("normalizeRefundStatus() = %q, want refunded", status)
	}
}

func TestNormalizeRefundStatusKeepsUnknownWithoutCodeAsRefunding(t *testing.T) {
	status := normalizeRefundStatus(map[string]string{
		"status": "processing",
	})
	if status != "refunding" {
		t.Fatalf("normalizeRefundStatus() = %q, want refunding", status)
	}
}

func TestCreatePaymentOmitsCounterParamWhenPayModeBlank(t *testing.T) {
	body := captureCreatePaymentBody(t, "")
	reqData := body["req_data"].(map[string]any)
	if _, ok := reqData["counter_param"]; ok {
		t.Fatalf("counter_param exists when pay_mode is blank: %#v", reqData["counter_param"])
	}
}

func TestCreatePaymentSetsCounterParamWhenPayModeConfigured(t *testing.T) {
	body := captureCreatePaymentBody(t, "WECHAT")
	reqData := body["req_data"].(map[string]any)
	if reqData["counter_param"] != `{"pay_mode":"WECHAT"}` {
		t.Fatalf("counter_param = %#v, want WECHAT", reqData["counter_param"])
	}
}

func TestCreatePaymentTrimsGatewayURLWhitespace(t *testing.T) {
	body := captureCreatePaymentBody(t, "", "\t")
	reqData := body["req_data"].(map[string]any)
	if reqData["out_order_no"] != "ORDER1" {
		t.Fatalf("out_order_no = %#v, want ORDER1", reqData["out_order_no"])
	}
}

func TestCreatePaymentUsesShanghaiTimeWhenLocalIsUTC(t *testing.T) {
	oldLocal := time.Local
	time.Local = time.UTC
	defer func() {
		time.Local = oldLocal
	}()

	loc := timeutil.ShanghaiLocation()
	before := time.Now().In(loc)
	body := captureCreatePaymentBody(t, "")
	after := time.Now().In(loc)
	reqData := body["req_data"].(map[string]any)

	reqTime := parseLakalaTestTime(t, body["req_time"].(string))
	if reqTime.Before(before.Add(-2*time.Second)) || reqTime.After(after.Add(2*time.Second)) {
		t.Fatalf("req_time = %s, want between %s and %s", reqTime, before, after)
	}

	efficientTime := parseLakalaTestTime(t, reqData["order_efficient_time"].(string))
	wantMin := before.Add(15*time.Minute - 2*time.Second)
	wantMax := after.Add(15*time.Minute + 2*time.Second)
	if efficientTime.Before(wantMin) || efficientTime.After(wantMax) {
		t.Fatalf("order_efficient_time = %s, want between %s and %s", efficientTime, wantMin, wantMax)
	}
}

func TestCreateRefundReturnsRawRequestOnHTTPError(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != endpointRefund {
			t.Fatalf("path = %s, want %s", r.URL.Path, endpointRefund)
		}
		w.WriteHeader(http.StatusBadRequest)
		_, _ = w.Write([]byte(`{"code":"OP90001","msg":"请求服务失败"}`))
	}))
	defer server.Close()

	key, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		t.Fatalf("generate key: %v", err)
	}
	privatePEM, certPEM := testKeyPairPEM(t, key)
	client := NewClient(Config{
		GatewayURL: server.URL,
		AppID:      "app-1",
		SerialNo:   "serial-1",
		MerchantID: "merchant-1",
		TermNo:     "term-1",
		PrivateKey: privatePEM,
		NotifyCert: certPEM,
	})
	resp, err := client.CreateRefund(context.Background(), CreateRefundRequest{
		ThirdOrderID:     "PAY123",
		ProviderOrderID:  "TRADE123",
		MerchantRefundID: "REF123",
		RefundAmountCent: 99,
		RefundReason:     "结账退款",
	})
	if err == nil {
		t.Fatal("CreateRefund error = nil, want HTTP error")
	}
	if resp == nil {
		t.Fatal("CreateRefund response = nil, want raw request")
	}
	if resp.RawRequest["merchant_no"] != "merchant-1" ||
		resp.RawRequest["term_no"] != "term-1" ||
		resp.RawRequest["out_trade_no"] != "REF123" ||
		resp.RawRequest["refund_amount"] != "99" ||
		resp.RawRequest["origin_out_trade_no"] != "PAY123" ||
		resp.RawRequest["origin_trade_no"] != "TRADE123" {
		t.Fatalf("raw request = %#v", resp.RawRequest)
	}
}

func parseLakalaTestTime(t *testing.T, value string) time.Time {
	t.Helper()
	parsed, err := time.ParseInLocation("20060102150405", value, timeutil.ShanghaiLocation())
	if err != nil {
		t.Fatalf("parse lakala time %q: %v", value, err)
	}
	return parsed
}

func testKeyPairPEM(t *testing.T, key *rsa.PrivateKey) (string, string) {
	t.Helper()
	privateDER := x509.MarshalPKCS1PrivateKey(key)
	privatePEM := string(pem.EncodeToMemory(&pem.Block{
		Type:  "RSA PRIVATE KEY",
		Bytes: privateDER,
	}))
	template := &x509.Certificate{}
	certDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
	if err != nil {
		t.Fatalf("create certificate: %v", err)
	}
	certPEM := string(pem.EncodeToMemory(&pem.Block{
		Type:  "CERTIFICATE",
		Bytes: certDER,
	}))
	return privatePEM, certPEM
}

func captureCreatePaymentBody(t *testing.T, payMode string, gatewaySuffix ...string) map[string]any {
	t.Helper()
	var captured map[string]any
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != endpointCounterCreate {
			t.Fatalf("path = %s, want %s", r.URL.Path, endpointCounterCreate)
		}
		if err := json.NewDecoder(r.Body).Decode(&captured); err != nil {
			t.Fatalf("decode request: %v", err)
		}
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"resp_code":"0","result_code":"0","pay_order_no":"LK1"}`))
	}))
	defer server.Close()

	key, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		t.Fatalf("generate key: %v", err)
	}
	privatePEM, certPEM := testKeyPairPEM(t, key)
	gatewayURL := server.URL
	if len(gatewaySuffix) > 0 {
		gatewayURL += gatewaySuffix[0]
	}
	client := NewClient(Config{
		GatewayURL: gatewayURL,
		AppID:      "app-1",
		SerialNo:   "serial-1",
		MerchantID: "merchant-1",
		PrivateKey: privatePEM,
		NotifyCert: certPEM,
		PayMode:    payMode,
	})
	_, err = client.CreatePayment(context.Background(), CreatePaymentRequest{
		ThirdOrderID: "ORDER1",
		AmountCent:   100,
		NotifyURL:    "https://example.com/notify",
		Body:         "测试订单",
	})
	if err != nil {
		t.Fatalf("CreatePayment error = %v", err)
	}
	return captured
}
