package alipay

import (
	"context"
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func TestAlipayClientCreatePagePayURLSignsRequest(t *testing.T) {
	t.Parallel()

	privateKey, privateKeyPEM, publicKeyBase64 := testAlipayKeys(t)
	client := NewClient(Config{
		AppID:           "2021000000000000",
		AppPrivateKey:   privateKeyPEM,
		AlipayPublicKey: publicKeyBase64,
		GatewayURL:      "https://openapi.alipay.com/gateway.do",
	}, nil)
	client.now = func() time.Time {
		return time.Date(2026, 6, 3, 12, 0, 0, 0, time.UTC)
	}

	payURL, err := client.CreatePagePayURL(PagePayInput{
		OutTradeNo:     "agentteam_123",
		Subject:        "AgentTeam hosted model credits",
		Amount:         "30.00",
		Body:           "test",
		TimeoutExpress: "30m",
		ReturnURL:      "http://localhost:5173/alipay/return",
		NotifyURL:      "https://agentteam.pingcap.com/alipay/notify",
	})
	require.NoError(t, err)

	parsed, err := url.Parse(payURL)
	require.NoError(t, err)
	require.Equal(t, "openapi.alipay.com", parsed.Host)

	query := parsed.Query()
	signature := query.Get("sign")
	require.NotEmpty(t, signature)
	params := make(map[string]string)
	for key, values := range query {
		if len(values) > 0 {
			params[key] = values[0]
		}
	}
	content := canonicalAlipayParams(params)
	decodedSignature, err := base64.StdEncoding.DecodeString(signature)
	require.NoError(t, err)
	hash := sha256.Sum256([]byte(content))
	require.NoError(t, rsa.VerifyPKCS1v15(&privateKey.PublicKey, crypto.SHA256, hash[:], decodedSignature))
	require.Contains(t, query.Get("biz_content"), `"out_trade_no":"agentteam_123"`)
	require.Contains(t, query.Get("biz_content"), `"timeout_express":"30m"`)
	require.Equal(t, "2026-06-03 12:00:00", query.Get("timestamp"))
}

func TestAlipayClientCreatePrecreateOrderReturnsQRCode(t *testing.T) {
	t.Parallel()

	privateKey, privateKeyPEM, publicKeyBase64 := testAlipayKeys(t)
	httpClient := &http.Client{
		Transport: roundTripFunc(func(request *http.Request) (*http.Response, error) {
			require.Equal(t, http.MethodPost, request.Method)

			body, err := io.ReadAll(request.Body)
			require.NoError(t, err)
			form, err := url.ParseQuery(string(body))
			require.NoError(t, err)
			require.Equal(t, alipayMethodPrecreate, form.Get("method"))
			require.Equal(t, "https://agentteam.pingcap.com/alipay/notify", form.Get("notify_url"))
			require.Contains(t, form.Get("biz_content"), `"out_trade_no":"agentteam_456"`)

			signature := form.Get("sign")
			require.NotEmpty(t, signature)
			params := make(map[string]string)
			for key, values := range form {
				if len(values) > 0 {
					params[key] = values[0]
				}
			}
			content := canonicalAlipayParams(params)
			decodedSignature, err := base64.StdEncoding.DecodeString(signature)
			require.NoError(t, err)
			hash := sha256.Sum256([]byte(content))
			require.NoError(t, rsa.VerifyPKCS1v15(&privateKey.PublicKey, crypto.SHA256, hash[:], decodedSignature))

			responseRaw := []byte(`{"code":"10000","msg":"Success","out_trade_no":"agentteam_456","qr_code":"https://qr.alipay.com/bax-test"}`)
			responseSignature, err := signRSA2(privateKey, responseRaw)
			require.NoError(t, err)
			return &http.Response{
				StatusCode: http.StatusOK,
				Header:     make(http.Header),
				Body: io.NopCloser(strings.NewReader(fmt.Sprintf(
					`{"alipay_trade_precreate_response":%s,"sign":%q}`,
					responseRaw,
					responseSignature,
				))),
			}, nil
		}),
	}
	client := NewClient(Config{
		AppID:           "2021000000000000",
		AppPrivateKey:   privateKeyPEM,
		AlipayPublicKey: publicKeyBase64,
		GatewayURL:      "https://openapi.alipay.com/gateway.do",
	}, httpClient)

	result, err := client.CreatePrecreateOrder(context.Background(), PrecreateInput{
		OutTradeNo: "agentteam_456",
		Subject:    "AgentTeam hosted model credits",
		Amount:     "30.00",
		Body:       "credits",
		NotifyURL:  "https://agentteam.pingcap.com/alipay/notify",
	})
	require.NoError(t, err)
	require.Equal(t, "agentteam_456", result.OutTradeNo)
	require.Equal(t, "https://qr.alipay.com/bax-test", result.QRCode)
	require.Equal(t, "10000", result.Code)
}

func TestAlipayClientParseQueryResponseVerifiesSignature(t *testing.T) {
	t.Parallel()

	privateKey, _, publicKeyBase64 := testAlipayKeys(t)
	client := NewClient(Config{
		AppID:           "2021000000000000",
		AppPrivateKey:   "unused",
		AlipayPublicKey: publicKeyBase64,
		GatewayURL:      "https://openapi.alipay.com/gateway.do",
	}, nil)
	responseRaw := []byte(`{"code":"10000","msg":"Success","out_trade_no":"agentteam_123","trade_no":"202606032200","trade_status":"TRADE_SUCCESS","total_amount":"30.00","send_pay_date":"2026-06-03 18:00:00"}`)
	signature, err := signRSA2(privateKey, responseRaw)
	require.NoError(t, err)
	envelope := []byte(fmt.Sprintf(
		`{"alipay_trade_query_response":%s,"sign":%q}`,
		responseRaw,
		signature,
	))

	result, err := client.parseQueryResponse(envelope)
	require.NoError(t, err)
	require.Equal(t, "agentteam_123", result.OutTradeNo)
	require.Equal(t, "202606032200", result.TradeNo)
	require.Equal(t, "TRADE_SUCCESS", result.TradeStatus)
	require.Equal(t, "30.00", result.TotalAmount)
	require.Equal(t, "2026-06-03 18:00:00", result.SendPayDate)
	require.Equal(t, sha256HexForTest(envelope), result.RawPayloadSHA256)
	require.Equal(t, "10000", result.Code)
}

func TestAlipayClientParseQueryResponseKeepsSubCode(t *testing.T) {
	t.Parallel()

	privateKey, _, publicKeyBase64 := testAlipayKeys(t)
	client := NewClient(Config{
		AppID:           "2021000000000000",
		AppPrivateKey:   "unused",
		AlipayPublicKey: publicKeyBase64,
		GatewayURL:      "https://openapi.alipay.com/gateway.do",
	}, nil)
	responseRaw := []byte(`{"code":"40004","msg":"Business Failed","sub_code":"ACQ.TRADE_NOT_EXIST","sub_msg":"交易不存在","out_trade_no":"agentteam_123"}`)
	signature, err := signRSA2(privateKey, responseRaw)
	require.NoError(t, err)
	envelope := []byte(fmt.Sprintf(
		`{"alipay_trade_query_response":%s,"sign":%q}`,
		responseRaw,
		signature,
	))

	result, err := client.parseQueryResponse(envelope)
	require.NoError(t, err)
	require.Equal(t, "agentteam_123", result.OutTradeNo)
	require.Equal(t, "40004", result.Code)
	require.Equal(t, "ACQ.TRADE_NOT_EXIST", result.SubCode)
	require.Equal(t, "交易不存在", result.SubMessage)
	require.Contains(t, result.Message, "ACQ.TRADE_NOT_EXIST")
}

func TestAlipayClientCloseTradeSignsAndParsesResponse(t *testing.T) {
	t.Parallel()

	privateKey, privateKeyPEM, publicKeyBase64 := testAlipayKeys(t)
	httpClient := &http.Client{
		Transport: roundTripFunc(func(request *http.Request) (*http.Response, error) {
			require.Equal(t, http.MethodPost, request.Method)

			body, err := io.ReadAll(request.Body)
			require.NoError(t, err)
			form, err := url.ParseQuery(string(body))
			require.NoError(t, err)
			require.Equal(t, alipayMethodClose, form.Get("method"))
			require.Contains(t, form.Get("biz_content"), `"out_trade_no":"seat_pay_123"`)

			signature := form.Get("sign")
			require.NotEmpty(t, signature)
			params := make(map[string]string)
			for key, values := range form {
				if len(values) > 0 {
					params[key] = values[0]
				}
			}
			content := canonicalAlipayParams(params)
			decodedSignature, err := base64.StdEncoding.DecodeString(signature)
			require.NoError(t, err)
			hash := sha256.Sum256([]byte(content))
			require.NoError(t, rsa.VerifyPKCS1v15(&privateKey.PublicKey, crypto.SHA256, hash[:], decodedSignature))

			responseRaw := []byte(`{"code":"10000","msg":"Success","out_trade_no":"seat_pay_123","trade_no":"202606032200"}`)
			responseSignature, err := signRSA2(privateKey, responseRaw)
			require.NoError(t, err)
			return &http.Response{
				StatusCode: http.StatusOK,
				Header:     make(http.Header),
				Body: io.NopCloser(strings.NewReader(fmt.Sprintf(
					`{"alipay_trade_close_response":%s,"sign":%q}`,
					responseRaw,
					responseSignature,
				))),
			}, nil
		}),
	}
	client := NewClient(Config{
		AppID:           "2021000000000000",
		AppPrivateKey:   privateKeyPEM,
		AlipayPublicKey: publicKeyBase64,
		GatewayURL:      "https://openapi.alipay.com/gateway.do",
	}, httpClient)

	result, err := client.CloseTrade(context.Background(), "seat_pay_123")
	require.NoError(t, err)
	require.Equal(t, "seat_pay_123", result.OutTradeNo)
	require.Equal(t, "202606032200", result.TradeNo)
	require.Equal(t, "10000", result.Code)
}

func TestAlipayClientVerifyNotifyRequiresMatchingAppID(t *testing.T) {
	t.Parallel()

	privateKey, _, publicKeyBase64 := testAlipayKeys(t)
	client := NewClient(Config{
		AppID:           "2021000000000000",
		AlipayPublicKey: publicKeyBase64,
		GatewayURL:      "https://openapi.alipay.com/gateway.do",
	}, nil)
	form := url.Values{
		"app_id":       {"2021000000000000"},
		"out_trade_no": {"agentteam_123"},
		"trade_status": {"TRADE_SUCCESS"},
		"total_amount": {"30.00"},
	}
	signature, err := signRSA2(privateKey, []byte(canonicalAlipayValues(form)))
	require.NoError(t, err)
	form.Set("sign", signature)
	form.Set("sign_type", alipaySignType)

	require.NoError(t, client.VerifyNotify(form))

	form.Set("app_id", "2021000000000001")
	signature, err = signRSA2(privateKey, []byte(canonicalAlipayValues(form)))
	require.NoError(t, err)
	form.Set("sign", signature)

	require.ErrorContains(t, client.VerifyNotify(form), "app_id")
}

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

func (fn roundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) {
	return fn(request)
}

func testAlipayKeys(t *testing.T) (*rsa.PrivateKey, string, string) {
	t.Helper()

	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	require.NoError(t, err)

	privateKeyPEM := string(pem.EncodeToMemory(&pem.Block{
		Type:  "RSA PRIVATE KEY",
		Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
	}))
	publicKeyDER, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
	require.NoError(t, err)
	return privateKey, privateKeyPEM, base64.StdEncoding.EncodeToString(publicKeyDER)
}

func sha256HexForTest(raw []byte) string {
	sum := sha256.Sum256(raw)
	return fmt.Sprintf("%x", sum[:])
}
