package payment

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"time"

	"kra/internal/biz"

	"github.com/go-pay/gopay"
	gopayAlipay "github.com/go-pay/gopay/alipay"
	"github.com/go-pay/gopay/pkg/xhttp"
)

type alipayAdapter struct{}

type alipayClient struct {
	client   *gopayAlipay.Client
	gateway  *url.URL
	response bytes.Buffer
}

type alipayTransport struct {
	base     http.RoundTripper
	gateway  *url.URL
	response *bytes.Buffer
}

type alipayResponseBody struct {
	io.Reader
	io.Closer
}

func (a *alipayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) {
	if req == nil {
		return nil, errors.New("支付宝下单参数为空")
	}
	amount, err := formatDecimalAmount(req.Amount, 100)
	if err != nil {
		return nil, err
	}
	bm := gopay.BodyMap{"out_trade_no": req.TradeNo, "total_amount": amount, "subject": req.Subject}
	mergeMap(bm, req.Extra,
		"out_trade_no", "total_amount", "subject", "notify_url", "return_url",
		"method", "pay_method", "trade_type", "channel",
	)
	sdk, err := newAlipayClient(c)
	if err != nil {
		return nil, err
	}
	if req.NotifyURL != "" {
		sdk.client.SetNotifyUrl(req.NotifyURL)
	}
	if req.ReturnURL != "" {
		sdk.client.SetReturnUrl(req.ReturnURL)
	}

	method, err := alipayCreateMethod(req.Extra, c)
	if err != nil {
		return nil, err
	}
	switch method {
	case "alipay.trade.pay":
		rsp, callErr := sdk.client.TradePay(ctx, bm)
		if err = sdk.finish(method, rsp != nil && rsp.Response != nil, callErr, c); err != nil {
			return nil, err
		}
		response := rsp.Response
		if response.OutTradeNo != "" && strings.TrimSpace(response.OutTradeNo) != req.TradeNo {
			return nil, errors.New("支付宝付款码支付响应的 out_trade_no 不匹配")
		}
		status := ""
		switch response.Code {
		case "10000":
			status = "success"
		case "10003":
			status = "pending"
		default:
			return nil, fmt.Errorf("支付宝付款码支付失败: %s", firstNonEmptyAlipay(response.SubMsg, response.Msg, response.SubCode, response.Code))
		}
		if strings.TrimSpace(response.OutTradeNo) == "" {
			return nil, errors.New("支付宝付款码支付响应缺少 out_trade_no")
		}
		if status == "success" && strings.TrimSpace(response.TradeNo) == "" {
			return nil, errors.New("支付宝付款码支付成功响应缺少 trade_no")
		}
		return &biz.PaymentResult{
			Provider: biz.PaymentAlipay, Status: status, TradeNo: req.TradeNo,
			ProviderTradeNo: strings.TrimSpace(response.TradeNo), Payload: sdk.payload(rsp),
		}, nil
	case "alipay.trade.precreate":
		rsp, callErr := sdk.client.TradePrecreate(ctx, bm)
		if err = sdk.finish(method, rsp != nil && rsp.Response != nil, callErr, c); err != nil {
			return nil, err
		}
		response := rsp.Response
		queryID := strings.TrimSpace(response.OutTradeNo)
		if queryID == "" {
			queryID = req.TradeNo
		} else if queryID != req.TradeNo {
			return nil, errors.New("支付宝预创建响应的 out_trade_no 不匹配")
		}
		if strings.TrimSpace(response.QrCode) == "" {
			return nil, errors.New("支付宝预创建响应缺少 qr_code")
		}
		// Precreate has no Alipay trade number yet. Keep the QR code in Payload
		// and query later by the merchant out_trade_no.
		return &biz.PaymentResult{
			Provider: biz.PaymentAlipay, Status: "created", TradeNo: req.TradeNo,
			QueryID: queryID, Payload: sdk.payload(rsp),
		}, nil
	case "alipay.trade.app.pay":
		orderString, callErr := sdk.client.TradeAppPay(ctx, bm)
		if callErr != nil {
			return nil, callErr
		}
		if strings.TrimSpace(orderString) == "" {
			return nil, errors.New("支付宝 APP 支付参数为空")
		}
		return &biz.PaymentResult{
			Provider: biz.PaymentAlipay, Status: "created", TradeNo: req.TradeNo,
			Payload: mustMarshalAlipayPayload(map[string]string{"order_string": orderString}),
		}, nil
	case "alipay.trade.page.pay":
		payURL, callErr := sdk.client.TradePagePay(ctx, bm)
		if callErr != nil {
			return nil, callErr
		}
		if strings.TrimSpace(payURL) == "" {
			return nil, errors.New("支付宝电脑网站支付 URL 为空")
		}
		payURL, err = sdk.rewriteGeneratedURL(payURL)
		if err != nil {
			return nil, err
		}
		return &biz.PaymentResult{
			Provider: biz.PaymentAlipay, Status: "created", TradeNo: req.TradeNo,
			Payload: mustMarshalAlipayPayload(map[string]string{"pay_url": payURL}),
		}, nil
	case "alipay.trade.wap.pay":
		payURL, callErr := sdk.client.TradeWapPay(ctx, bm)
		if callErr != nil {
			return nil, callErr
		}
		if strings.TrimSpace(payURL) == "" {
			return nil, errors.New("支付宝手机网站支付 URL 为空")
		}
		payURL, err = sdk.rewriteGeneratedURL(payURL)
		if err != nil {
			return nil, err
		}
		return &biz.PaymentResult{
			Provider: biz.PaymentAlipay, Status: "created", TradeNo: req.TradeNo,
			Payload: mustMarshalAlipayPayload(map[string]string{"pay_url": payURL}),
		}, nil
	default:
		rsp, callErr := sdk.client.TradeCreate(ctx, bm)
		if err = sdk.finish(method, rsp != nil && rsp.Response != nil, callErr, c); err != nil {
			return nil, err
		}
		response := rsp.Response
		if response.OutTradeNo != "" && strings.TrimSpace(response.OutTradeNo) != req.TradeNo {
			return nil, errors.New("支付宝创建响应的 out_trade_no 不匹配")
		}
		providerTradeNo := strings.TrimSpace(response.TradeNo)
		if providerTradeNo == "" {
			return nil, errors.New("支付宝创建响应缺少 trade_no")
		}
		return &biz.PaymentResult{
			Provider: biz.PaymentAlipay, Status: "created", TradeNo: req.TradeNo,
			ProviderTradeNo: providerTradeNo, Payload: sdk.payload(rsp),
		}, nil
	}
}

func (a *alipayAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) {
	tradeNo = strings.TrimSpace(tradeNo)
	if tradeNo == "" {
		return nil, errors.New("支付宝查单缺少 out_trade_no")
	}
	sdk, err := newAlipayClient(c)
	if err != nil {
		return nil, err
	}
	const method = "alipay.trade.query"
	rsp, callErr := sdk.client.TradeQuery(ctx, gopay.BodyMap{"out_trade_no": tradeNo})
	if err = sdk.finish(method, rsp != nil && rsp.Response != nil, callErr, c); err != nil {
		return nil, err
	}
	response := rsp.Response
	result := &biz.PaymentResult{
		Provider: biz.PaymentAlipay, Status: normalizePaymentStatus(response.TradeStatus, "pending"),
		TradeNo: strings.TrimSpace(response.OutTradeNo), ProviderTradeNo: strings.TrimSpace(response.TradeNo),
		Payload: sdk.payload(rsp),
	}
	if result.TradeNo == "" {
		result.TradeNo = tradeNo
	} else if result.TradeNo != tradeNo {
		return nil, errors.New("支付宝查询响应的 out_trade_no 不匹配")
	}
	if result.Status != "success" {
		return result, nil
	}
	if result.ProviderTradeNo == "" {
		return nil, errors.New("支付宝查询响应缺少 trade_no")
	}
	result.Amount, err = parseDecimalAmount(response.TotalAmount, 100)
	if err != nil {
		return nil, fmt.Errorf("解析支付宝订单金额: %w", err)
	}
	result.Currency = strings.ToUpper(firstNonEmptyAlipay(response.TransCurrency, response.PayCurrency))
	if result.Currency == "" {
		result.Currency = "CNY"
	}
	values, err := alipayResponseValues(response)
	if err != nil {
		return nil, err
	}
	if err = populateAlipayBreakdown(result, values); err != nil {
		return nil, err
	}
	return result, nil
}

func (a *alipayAdapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) {
	if req == nil {
		return nil, errors.New("支付宝退款请求为空")
	}
	tradeNo := strings.TrimSpace(req.TradeNo)
	refundNo := strings.TrimSpace(req.RefundNo)
	if tradeNo == "" || refundNo == "" {
		return nil, errors.New("支付宝退款缺少 out_trade_no 或 out_request_no")
	}
	formatted, err := formatDecimalAmount(req.Amount, 100)
	if err != nil {
		return nil, err
	}
	sdk, err := newAlipayClient(c)
	if err != nil {
		return nil, err
	}
	bm := gopay.BodyMap{"out_trade_no": tradeNo, "out_request_no": refundNo, "refund_amount": formatted}
	mergeGoPayConfigExtras(bm, c, "refund_extra", "out_trade_no", "out_request_no", "refund_amount")
	const method = "alipay.trade.refund"
	rsp, callErr := sdk.client.TradeRefund(ctx, bm)
	if err = sdk.finish(method, rsp != nil && rsp.Response != nil, callErr, c); err != nil {
		return nil, err
	}
	response := rsp.Response
	return alipayRefundResult(req, response, sdk.payload(rsp))
}

func alipayRefundResult(req *biz.PaymentRefundRequest, response *gopayAlipay.TradeRefund, payload []byte) (*biz.PaymentResult, error) {
	if req == nil || response == nil {
		return nil, errors.New("支付宝退款响应为空")
	}
	tradeNo := strings.TrimSpace(req.TradeNo)
	returnedTradeNo := strings.TrimSpace(response.OutTradeNo)
	if returnedTradeNo == "" {
		return nil, errors.New("支付宝退款响应缺少 out_trade_no")
	}
	if returnedTradeNo != tradeNo {
		return nil, errors.New("支付宝退款响应的 out_trade_no 不匹配")
	}
	providerTradeNo := strings.TrimSpace(response.TradeNo)
	if providerTradeNo == "" {
		return nil, errors.New("支付宝退款响应缺少 trade_no")
	}
	if expected := strings.TrimSpace(req.ProviderTradeNo); expected != "" && providerTradeNo != expected {
		return nil, errors.New("支付宝退款响应的 trade_no 不匹配")
	}
	refundFee := strings.TrimSpace(response.RefundFee)
	if refundFee == "" {
		return nil, errors.New("支付宝退款响应缺少 refund_fee")
	}
	amount, err := parseDecimalAmount(refundFee, 100)
	if err != nil {
		return nil, fmt.Errorf("解析支付宝退款金额: %w", err)
	}
	if amount != req.Amount {
		return nil, errors.New("支付宝退款响应金额不匹配")
	}
	currency := strings.ToUpper(strings.TrimSpace(response.RefundCurrency))
	requestCurrency := strings.ToUpper(strings.TrimSpace(req.Currency))
	if currency == "" {
		currency = requestCurrency
	} else if requestCurrency != "" && currency != requestCurrency {
		return nil, errors.New("支付宝退款响应币种不匹配")
	}
	if currency == "" {
		currency = "CNY"
	}
	return &biz.PaymentResult{
		Provider: biz.PaymentAlipay, Status: "created", TradeNo: tradeNo,
		ProviderTradeNo: providerTradeNo, Amount: amount, Currency: currency, Payload: payload,
	}, nil
}

func (a *alipayAdapter) Callback(_ context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) {
	if callback == nil || len(callback.Body) == 0 {
		return nil, errors.New("支付宝回调为空")
	}
	fields := callbackFields(callback)
	if err := verifyAlipay(fields, c); err != nil {
		return nil, err
	}
	if fields["app_id"] != text(c, "app_id") {
		return nil, errors.New("支付宝回调 app_id 不匹配")
	}
	status := "pending"
	if fields["trade_status"] == "TRADE_SUCCESS" || fields["trade_status"] == "TRADE_FINISHED" {
		status = "success"
	}
	payload, _ := json.Marshal(fields)
	return &biz.PaymentResult{
		Provider: biz.PaymentAlipay, Status: status,
		TradeNo: first(fields, "out_trade_no"), ProviderTradeNo: first(fields, "trade_no"),
		Payload: payload,
	}, nil
}
func newAlipayClient(c map[string]any) (*alipayClient, error) {
	appID := strings.TrimSpace(text(c, "app_id"))
	privateKey := strings.TrimSpace(text(c, "private_key"))
	if appID == "" || privateKey == "" {
		return nil, errors.New("支付宝缺少 app_id 或 private_key")
	}
	production := !strings.EqualFold(text(c, "environment"), "sandbox")
	client, err := gopayAlipay.NewClient(appID, goPayAlipayKey(privateKey, false), production)
	if err != nil {
		return nil, err
	}
	if charset := strings.TrimSpace(text(c, "charset")); charset != "" {
		client.SetCharset(charset)
	}
	if signType := strings.TrimSpace(text(c, "sign_type")); signType != "" {
		client.SetSignType(signType)
	}
	if token := strings.TrimSpace(text(c, "app_auth_token")); token != "" {
		client.SetAppAuthToken(token)
	}
	if location := strings.TrimSpace(text(c, "location")); location != "" {
		client.SetLocation(location)
	}

	sdk := &alipayClient{client: client}
	if endpoint := strings.TrimSpace(text(c, "gateway_url")); endpoint != "" {
		sdk.gateway, err = url.Parse(endpoint)
		if err != nil || sdk.gateway.Scheme == "" || sdk.gateway.Host == "" {
			return nil, fmt.Errorf("支付宝 gateway_url 无效: %s", endpoint)
		}
	}
	httpClient := xhttp.NewClient().SetTimeout(20 * time.Second).SetBodySize(8)
	httpClient.SetTransport(&alipayTransport{
		base: httpClient.HttpClient.Transport, gateway: sdk.gateway, response: &sdk.response,
	})
	client.SetHttpClient(httpClient)
	return sdk, nil
}

func (c *alipayClient) finish(method string, hasResponse bool, callErr error, config map[string]any) error {
	if !hasResponse {
		if callErr != nil {
			return callErr
		}
		return fmt.Errorf("支付宝 %s 响应为空", method)
	}
	if c.response.Len() == 0 {
		return fmt.Errorf("支付宝 %s 响应原文为空", method)
	}
	if err := verifyAlipayResponse(c.response.Bytes(), method, config); err != nil {
		return err
	}
	return callErr
}

func (c *alipayClient) payload(fallback any) []byte {
	if c.response.Len() > 0 {
		return append([]byte(nil), c.response.Bytes()...)
	}
	return mustMarshalAlipayPayload(fallback)
}

func (c *alipayClient) rewriteGeneratedURL(value string) (string, error) {
	if c.gateway == nil {
		return value, nil
	}
	source, err := url.Parse(value)
	if err != nil {
		return "", fmt.Errorf("解析支付宝支付 URL: %w", err)
	}
	return alipayTargetURL(source, c.gateway).String(), nil
}

func (t *alipayTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	if t.base == nil {
		t.base = http.DefaultTransport
	}
	target := req
	if t.gateway != nil {
		clone := req.Clone(req.Context())
		clone.URL = alipayTargetURL(req.URL, t.gateway)
		clone.Host = ""
		target = clone
	}
	if t.response != nil {
		t.response.Reset()
	}
	resp, err := t.base.RoundTrip(target)
	if err != nil || resp == nil || resp.Body == nil || t.response == nil {
		return resp, err
	}
	resp.Body = &alipayResponseBody{Reader: io.TeeReader(resp.Body, t.response), Closer: resp.Body}
	return resp, nil
}

func alipayTargetURL(source, gateway *url.URL) *url.URL {
	target := *gateway
	switch {
	case target.RawQuery == "":
		target.RawQuery = source.RawQuery
	case source.RawQuery != "":
		target.RawQuery += "&" + source.RawQuery
	}
	return &target
}

func alipayCreateMethod(extra, config map[string]any) (string, error) {
	value := firstAny(extra, "method", "pay_method", "trade_type", "channel")
	if value == "" {
		value = firstAny(config, "method", "pay_method", "trade_type", "channel")
	}
	normalized := strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
	switch normalized {
	case "", "create", "trade_create", "alipay_trade_create", "jsapi", "miniapp", "mini_program":
		return "alipay.trade.create", nil
	case "pay", "trade_pay", "alipay_trade_pay", "barcode", "barcode_pay", "micropay", "face_to_face":
		return "alipay.trade.pay", nil
	case "precreate", "trade_precreate", "alipay_trade_precreate", "native", "qr", "qrcode", "scan":
		return "alipay.trade.precreate", nil
	case "app", "app_pay", "trade_app_pay", "alipay_trade_app_pay":
		return "alipay.trade.app.pay", nil
	case "page", "page_pay", "trade_page_pay", "alipay_trade_page_pay", "web", "pc", "desktop":
		return "alipay.trade.page.pay", nil
	case "wap", "wap_pay", "trade_wap_pay", "alipay_trade_wap_pay", "h5", "mobile":
		return "alipay.trade.wap.pay", nil
	default:
		return "", fmt.Errorf("支付宝不支持的下单方式: %s", value)
	}
}

func alipayResponseValues(value any) (map[string]any, error) {
	raw, err := json.Marshal(value)
	if err != nil {
		return nil, err
	}
	result := map[string]any{}
	if err = json.Unmarshal(raw, &result); err != nil {
		return nil, err
	}
	return result, nil
}

func mustMarshalAlipayPayload(value any) []byte {
	raw, _ := json.Marshal(value)
	return raw
}

func firstNonEmptyAlipay(values ...string) string {
	for _, value := range values {
		if value = strings.TrimSpace(value); value != "" {
			return value
		}
	}
	return ""
}

func populateAlipayBreakdown(result *biz.PaymentResult, response map[string]any) error {
	if result == nil || result.Status != "success" {
		return nil
	}
	// Alipay returns these values in major currency units. Keep all arithmetic
	// integer-only and only mark the breakdown complete when the core fields are
	// present and internally consistent.
	total, err := parseDecimalAmount(strings.TrimSpace(fmt.Sprint(response["total_amount"])), 100)
	if err != nil {
		return fmt.Errorf("解析支付宝 total_amount: %w", err)
	}
	result.Amount = total
	payer, payerOK, err := parseAlipayDecimal(response, "buyer_pay_amount")
	if err != nil {
		return err
	}
	if !payerOK {
		payer, payerOK, err = parseAlipayDecimal(response, "receipt_amount")
		if err != nil {
			return err
		}
	}
	point, pointOK, err := parseAlipayDecimal(response, "point_amount")
	if err != nil {
		return err
	}
	if !payerOK {
		return nil
	}
	if pointOK && point > payer {
		return errors.New("支付宝 point_amount 大于用户实付金额")
	}
	result.PayerPaidAmount = payer
	result.PointPaidAmount = point
	result.CashPaidAmount = payer - point
	result.DiscountAmount = total - payer
	if result.DiscountAmount < 0 {
		return errors.New("支付宝用户实付金额超过订单总额")
	}
	// receipt_amount is the merchant receipt amount. It is not always present;
	// when it is, use it as settlement amount. Alipay's detailed voucher list
	// is retained in Payload for audit and is intentionally not guessed here.
	if receipt, ok, receiptErr := parseAlipayDecimal(response, "receipt_amount"); receiptErr != nil {
		return receiptErr
	} else if ok {
		result.SettlementAmount = receipt
	}
	result.AmountBreakdownKnown = true
	return nil
}

func parseAlipayDecimal(values map[string]any, key string) (int64, bool, error) {
	value, exists := values[key]
	if !exists || strings.TrimSpace(fmt.Sprint(value)) == "" || fmt.Sprint(value) == "<nil>" {
		return 0, false, nil
	}
	amount, err := parseDecimalAmount(strings.TrimSpace(fmt.Sprint(value)), 100)
	return amount, true, err
}

func verifyAlipayResponse(raw []byte, method string, c map[string]any) error {
	publicKeyValue := text(c, "public_key")
	if publicKeyValue == "" {
		return errors.New("支付宝缺少 public_key")
	}
	var envelope map[string]json.RawMessage
	if err := json.Unmarshal(raw, &envelope); err != nil {
		return err
	}
	responseKey := strings.ReplaceAll(method, ".", "_") + "_response"
	responseRaw := envelope[responseKey]
	if len(responseRaw) == 0 {
		return errors.New("支付宝响应缺少 " + responseKey)
	}
	var sign string
	if err := json.Unmarshal(envelope["sign"], &sign); err != nil || sign == "" {
		return errors.New("支付宝响应缺少 sign")
	}
	ok, err := gopayAlipay.VerifySyncSign(goPayAlipayKey(publicKeyValue, true), string(responseRaw), sign)
	if err != nil {
		return err
	}
	if !ok {
		return errors.New("支付宝响应签名校验失败")
	}
	return nil
}

func alipaySignContent(v url.Values) string {
	keys := make([]string, 0, len(v))
	for key := range v {
		if key != "sign" && v.Get(key) != "" {
			keys = append(keys, key)
		}
	}
	sort.Strings(keys)
	parts := make([]string, 0, len(keys))
	for _, key := range keys {
		parts = append(parts, key+"="+v.Get(key))
	}
	return strings.Join(parts, "&")
}

func verifyAlipay(fields map[string]string, c map[string]any) error {
	if text(c, "app_id") == "" || text(c, "public_key") == "" || fields["sign"] == "" {
		return errors.New("支付宝回调验签配置或 sign 缺失")
	}
	values := url.Values{}
	for key, value := range fields {
		values.Set(key, value)
	}
	body, err := gopayAlipay.ParseNotifyByURLValues(values)
	if err != nil {
		return err
	}
	ok, err := gopayAlipay.VerifySign(goPayAlipayKey(text(c, "public_key"), true), cloneGoPayBodyMap(body))
	if err != nil {
		return err
	}
	if !ok {
		return errors.New("支付宝回调签名校验失败")
	}
	return nil
}
func mergeMap(dst map[string]any, src map[string]any, protected ...string) {
	protectedKeys := make(map[string]struct{}, len(protected))
	for _, key := range protected {
		protectedKeys[key] = struct{}{}
	}
	for key, value := range src {
		if _, exists := protectedKeys[key]; exists {
			continue
		}
		dst[key] = value
	}
}
