package payment

import (
	"bytes"
	"context"
	"crypto"
	"crypto/rsa"
	"crypto/sha1"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strings"
	"sync"

	"kra/internal/biz"

	"github.com/go-pay/crypto/xpem"
	"github.com/go-pay/crypto/xrsa"
	"github.com/go-pay/gopay"
	"github.com/go-pay/gopay/allinpay"
)

type allinpayAdapter struct{}

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

func (f allinpayRoundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
	return f(req)
}

type allinpayResponseCapture struct {
	mu   sync.Mutex
	body []byte
}

func (c *allinpayResponseCapture) set(body []byte) {
	c.mu.Lock()
	c.body = append(c.body[:0], body...)
	c.mu.Unlock()
}

func (c *allinpayResponseCapture) get() []byte {
	c.mu.Lock()
	defer c.mu.Unlock()
	return append([]byte(nil), c.body...)
}

// captureAllinpayResponse preserves the exact JSON returned by the gateway.
// Query and Refund in GoPay v1.5.122 unmarshal and return without invoking its
// internal verifySign, so adapter-level verification must use the raw bytes
// rather than re-marshalling a response struct.
func captureAllinpayResponse(client *allinpay.Client) (*allinpayResponseCapture, error) {
	if client == nil || client.GetHttpClient() == nil || client.GetHttpClient().HttpClient == nil {
		return nil, errors.New("通联支付 HTTP 客户端为空")
	}
	hc := client.GetHttpClient()
	base := hc.HttpClient.Transport
	if base == nil {
		base = http.DefaultTransport
	}
	capture := &allinpayResponseCapture{}
	hc.SetTransport(allinpayRoundTripperFunc(func(req *http.Request) (*http.Response, error) {
		res, err := base.RoundTrip(req)
		if err != nil {
			return nil, err
		}
		if res == nil || res.Body == nil {
			return res, nil
		}
		body, readErr := io.ReadAll(res.Body)
		_ = res.Body.Close()
		if readErr != nil {
			return nil, readErr
		}
		capture.set(body)
		res.Body = io.NopCloser(bytes.NewReader(body))
		return res, nil
	}))
	return capture, nil
}

func verifyAllinpayResponse(publicKey string, raw []byte) error {
	if len(bytes.TrimSpace(raw)) == 0 {
		return errors.New("通联支付响应签名校验失败: 响应为空")
	}
	values := gopay.BodyMap{}
	if err := json.Unmarshal(raw, &values); err != nil {
		return fmt.Errorf("通联支付响应签名校验失败: %w", err)
	}
	sign := strings.TrimSpace(values.GetString("sign"))
	if sign == "" {
		return errors.New("通联支付响应签名校验失败: 缺少 sign")
	}
	values.Remove("sign")
	key, err := xpem.DecodePublicKey([]byte(xrsa.FormatAlipayPublicKey(publicKey)))
	if err != nil {
		return fmt.Errorf("通联支付公钥解析失败: %w", err)
	}
	signature, err := base64.StdEncoding.DecodeString(sign)
	if err != nil {
		return fmt.Errorf("通联支付响应签名编码无效: %w", err)
	}
	digest := sha1.Sum([]byte(values.EncodeAliPaySignParams()))
	if err = rsa.VerifyPKCS1v15(key, crypto.SHA1, digest[:], signature); err != nil {
		return fmt.Errorf("通联支付响应签名校验失败: %w", err)
	}
	return nil
}

func (a *allinpayAdapter) client(c map[string]any) (*allinpay.Client, error) {
	client, err := allinpay.NewClient(text(c, "cus_id"), text(c, "app_id"), text(c, "private_key"), text(c, "public_key"), !strings.EqualFold(text(c, "environment"), "sandbox"))
	if err != nil {
		return nil, err
	}
	if org := text(c, "org_id"); org != "" {
		client.SetOrgId(org)
	}
	return client, nil
}

func (a *allinpayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) {
	if req == nil {
		return nil, errors.New("通联支付下单请求为空")
	}
	method, err := allinpayCreateMethod(req.Extra, c)
	if err != nil {
		return nil, err
	}
	orderType, err := allinpayOrderType(c)
	if err != nil {
		return nil, err
	}
	client, err := a.client(c)
	if err != nil {
		return nil, err
	}
	bm := gopay.BodyMap{"reqsn": req.TradeNo, "trxamt": req.Amount, "body": req.Subject}
	mergeGoPayExtras(bm, req.Extra, "reqsn", "trxamt", "body", "method", "pay_method", "trade_type")
	if method == "scan" {
		bm.Set("authcode", firstAny(req.Extra, "authcode", "auth_code"))
		bm.SetBodyMap("terminfo", func(info gopay.BodyMap) {
			info.Set("devicetype", firstAnyOr(req.Extra, "10", "device_type", "devicetype"))
			info.Set("termno", firstAnyOr(req.Extra, "00000001", "termno", "terminal_no"))
		})
		rsp, callErr := client.ScanPay(ctx, bm)
		if callErr != nil {
			return nil, callErr
		}
		if rsp == nil {
			return nil, errors.New("通联支付扫码下单响应为空")
		}
		queryID, err := allinpayCreateQueryID(orderType, rsp.Trxid)
		if err != nil {
			return nil, err
		}
		if returnedTradeNo := strings.TrimSpace(rsp.Reqsn); returnedTradeNo != "" && returnedTradeNo != strings.TrimSpace(req.TradeNo) {
			return nil, errors.New("通联支付扫码响应的 reqsn 不匹配")
		}
		return &biz.PaymentResult{Provider: biz.PaymentAllinPay, Status: normalizeAllinState(rsp.TrxStatus), TradeNo: req.TradeNo, ProviderTradeNo: strings.TrimSpace(rsp.Trxid), QueryID: queryID, Payload: mustJSON(rsp)}, nil
	}
	if method == "native" {
		if orderType == allinpay.OrderTypeTrxId {
			return nil, errors.New("通联支付 Native 下单不支持 query_order_type=trxid")
		}
		bm.Set("expiretime", firstAny(req.Extra, "expiretime", "expire_time"))
		rsp, callErr := client.NativePay(ctx, bm)
		if callErr != nil {
			return nil, callErr
		}
		if rsp == nil {
			return nil, errors.New("通联支付下单响应为空")
		}
		if returnedTradeNo := strings.TrimSpace(rsp.ReqSn); returnedTradeNo != "" && returnedTradeNo != strings.TrimSpace(req.TradeNo) {
			return nil, errors.New("通联支付 Native 响应的 reqsn 不匹配")
		}
		return &biz.PaymentResult{Provider: biz.PaymentAllinPay, Status: "created", TradeNo: req.TradeNo, Payload: mustJSON(rsp)}, nil
	}
	payType := firstAny(req.Extra, "paytype", "pay_type")
	if payType == "" {
		payType = firstAny(c, "paytype", "pay_type")
	}
	if payType == "" {
		payType = allinpay.PayTypeWXJS
	}
	bm.Set("paytype", payType)
	rsp, err := client.Pay(ctx, bm)
	if err != nil {
		return nil, err
	}
	if rsp == nil {
		return nil, errors.New("通联支付下单响应为空")
	}
	if returnedTradeNo := strings.TrimSpace(rsp.Reqsn); returnedTradeNo != "" && returnedTradeNo != strings.TrimSpace(req.TradeNo) {
		return nil, errors.New("通联支付下单响应的 reqsn 不匹配")
	}
	queryID, err := allinpayCreateQueryID(orderType, rsp.Trxid)
	if err != nil {
		return nil, err
	}
	return &biz.PaymentResult{Provider: biz.PaymentAllinPay, Status: "created", TradeNo: req.TradeNo, ProviderTradeNo: strings.TrimSpace(rsp.Trxid), QueryID: queryID, Payload: mustJSON(rsp)}, nil
}

func allinpayCreateMethod(extra, config map[string]any) (string, error) {
	value := firstAny(extra, "method", "pay_method", "trade_type")
	if value == "" {
		value = firstAny(config, "method", "pay_method", "trade_type")
	}
	normalized := strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
	switch normalized {
	case "", "pay", "unified", "unified_pay":
		return "pay", nil
	case "scan", "scan_pay", "micropay", "micro_pay", "barcode", "barcode_pay":
		return "scan", nil
	case "native", "native_pay", "qr", "qrcode":
		return "native", nil
	default:
		return "", fmt.Errorf("通联支付不支持的下单方式: %s", value)
	}
}

func (a *allinpayAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) {
	orderType, err := allinpayOrderType(c)
	if err != nil {
		return nil, err
	}
	tradeNo = strings.TrimSpace(tradeNo)
	if tradeNo == "" {
		return nil, errors.New("通联支付查单缺少订单号")
	}
	client, err := a.client(c)
	if err != nil {
		return nil, err
	}
	capture, err := captureAllinpayResponse(client)
	if err != nil {
		return nil, err
	}
	rsp, err := client.Query(ctx, orderType, tradeNo)
	if err != nil {
		return nil, err
	}
	if err = verifyAllinpayResponse(text(c, "public_key"), capture.get()); err != nil {
		return nil, err
	}
	if rsp == nil {
		return nil, errors.New("通联支付查单响应为空")
	}
	result := &biz.PaymentResult{Provider: biz.PaymentAllinPay, Status: normalizeAllinState(rsp.TrxStatus), TradeNo: strings.TrimSpace(rsp.Reqsn), ProviderTradeNo: strings.TrimSpace(rsp.Trxid), Currency: strings.ToUpper(firstAny(c, "currency")), Payload: mustJSON(rsp)}
	if strings.EqualFold(orderType, allinpay.OrderTypeTrxId) {
		if result.ProviderTradeNo == "" {
			return nil, errors.New("通联支付查单响应缺少 trxid")
		}
		if result.ProviderTradeNo != tradeNo {
			return nil, errors.New("通联支付查单响应的 trxid 不匹配")
		}
		result.QueryID = tradeNo
		if strings.TrimSpace(result.TradeNo) == "" {
			return nil, errors.New("通联支付查单响应缺少商户订单号")
		}
	} else if result.TradeNo == "" {
		result.TradeNo = tradeNo
	} else if result.TradeNo != tradeNo {
		return nil, errors.New("通联支付查单响应的 reqsn 不匹配")
	}
	if rsp.TrxAmt != "" {
		result.Amount, err = parseIntegerAmount(rsp.TrxAmt)
		if err != nil {
			if scale := configuredInt64(c, "amount_scale", 100); scale != 1 {
				result.Amount, err = parseDecimalAmount(rsp.TrxAmt, scale)
			}
		}
	}
	if result.Currency == "" {
		result.Currency = "CNY"
	}
	return result, err
}

func allinpayOrderType(c map[string]any) (string, error) {
	orderType := strings.ToLower(strings.TrimSpace(firstAny(c, "query_order_type", "order_type")))
	if orderType == "" {
		return allinpay.OrderTypeReqSN, nil
	}
	if orderType != allinpay.OrderTypeReqSN && orderType != allinpay.OrderTypeTrxId {
		return "", fmt.Errorf("通联支付 query_order_type 必须是 %s 或 %s", allinpay.OrderTypeReqSN, allinpay.OrderTypeTrxId)
	}
	return orderType, nil
}

func allinpayCreateQueryID(orderType, transactionID string) (string, error) {
	if orderType == allinpay.OrderTypeTrxId {
		queryID := strings.TrimSpace(transactionID)
		if queryID == "" {
			return "", errors.New("通联支付下单响应缺少 trxid，无法按 trxid 查单")
		}
		return queryID, nil
	}
	return "", nil
}

func (a *allinpayAdapter) 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 == "" || req.Amount <= 0 {
		return nil, errors.New("通联支付退款缺少商户订单号、退款单号或有效金额")
	}
	orderType, err := allinpayOrderType(c)
	if err != nil {
		return nil, err
	}
	queryID := strings.TrimSpace(req.QueryID)
	if orderType == allinpay.OrderTypeTrxId && queryID == "" {
		return nil, errors.New("通联支付按 trxid 退款缺少持久化 QueryID")
	}
	client, err := a.client(c)
	if err != nil {
		return nil, err
	}
	capture, err := captureAllinpayResponse(client)
	if err != nil {
		return nil, err
	}
	bm := gopay.BodyMap{"reqsn": refundNo, "trxamt": req.Amount, "remark": firstAny(c, "refund_remark", "remark")}
	if orderType == allinpay.OrderTypeTrxId {
		bm.Set("oldtrxid", queryID)
	} else {
		bm.Set("oldreqsn", tradeNo)
	}
	mergeGoPayConfigExtras(bm, c, "refund_extra", "reqsn", "trxamt", "oldreqsn", "oldtrxid", "remark")
	rsp, err := client.Refund(ctx, bm)
	if err != nil {
		return nil, err
	}
	if err = verifyAllinpayResponse(text(c, "public_key"), capture.get()); err != nil {
		return nil, err
	}
	effective := *req
	effective.TradeNo = tradeNo
	effective.RefundNo = refundNo
	return allinpayRefundResult(&effective, rsp, firstAny(c, "currency"))
}

func allinpayRefundResult(req *biz.PaymentRefundRequest, rsp *allinpay.RefundRsp, currency string) (*biz.PaymentResult, error) {
	if rsp == nil {
		return nil, errors.New("通联支付退款响应为空")
	}
	if returnedRefundNo := strings.TrimSpace(rsp.Reqsn); returnedRefundNo == "" {
		return nil, errors.New("通联支付退款响应缺少 reqsn")
	} else if returnedRefundNo != req.RefundNo {
		return nil, errors.New("通联支付退款响应的 reqsn 不匹配")
	}
	providerRefundID := strings.TrimSpace(rsp.Trxid)
	if providerRefundID == "" {
		return nil, errors.New("通联支付退款响应缺少 trxid")
	}
	if strings.TrimSpace(rsp.Fee) == "" {
		return nil, errors.New("通联支付退款响应缺少 fee")
	}
	refundAmount, err := parseIntegerAmount(rsp.Fee)
	if err != nil {
		return nil, fmt.Errorf("解析通联支付退款金额: %w", err)
	}
	if refundAmount != req.Amount {
		return nil, errors.New("通联支付退款响应金额不匹配")
	}
	return &biz.PaymentResult{Provider: biz.PaymentAllinPay, Status: normalizeRefundState(rsp.TrxStatus), TradeNo: req.TradeNo, ProviderTradeNo: providerRefundID, Amount: refundAmount, Currency: strings.ToUpper(strings.TrimSpace(currency)), Payload: mustJSON(rsp)}, nil
}

func (a *allinpayAdapter) Callback(_ context.Context, _ *biz.PaymentCallback, _ map[string]any) (*biz.PaymentResult, error) {
	return nil, errors.New("通联支付回调没有可复用的 GoPay 验签器，请改用主动查单")
}
