package payment

import (
	"bytes"
	"context"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"time"

	"kra/internal/biz"

	"github.com/go-pay/gopay"
	"github.com/go-pay/gopay/pkg/xhttp"
	gopayWechat "github.com/go-pay/gopay/wechat"
)

type wechatV2Adapter struct{}

type wechatV2EndpointTransport struct {
	endpoint *url.URL
	next     http.RoundTripper
}

func (t *wechatV2EndpointTransport) RoundTrip(request *http.Request) (*http.Response, error) {
	clone := request.Clone(request.Context())
	target := *t.endpoint
	clone.URL = &target
	clone.Host = target.Host
	clone.RequestURI = ""
	return t.next.RoundTrip(clone)
}

func wechatV2SignType(value string) string {
	if strings.EqualFold(strings.TrimSpace(value), gopayWechat.SignType_HMAC_SHA256) {
		return gopayWechat.SignType_HMAC_SHA256
	}
	return gopayWechat.SignType_MD5
}

func wechatV2Sign(values map[string]string, key, signType string) string {
	body := toGoPayBodyMap(values)
	body.Remove("sign")
	return gopayWechat.GetReleaseSign(key, wechatV2SignType(signType), body)
}

func (a *wechatV2Adapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) {
	if req == nil {
		return nil, errors.New("微信支付 v2 下单请求为空")
	}
	client, err := newWechatV2Client(c, firstAny(c, "create_url", "unified_order_url"))
	if err != nil {
		return nil, err
	}
	tradeType, err := wechatV2CreateMethod(req.Extra, c)
	if err != nil {
		return nil, err
	}
	values := map[string]string{
		"appid":            client.AppId,
		"mch_id":           client.MchId,
		"nonce_str":        nonce(),
		"body":             req.Subject,
		"out_trade_no":     req.TradeNo,
		"total_fee":        fmt.Sprint(req.Amount),
		"fee_type":         strings.ToUpper(req.Currency),
		"spbill_create_ip": req.ClientIP,
		"notify_url":       req.NotifyURL,
		"trade_type":       tradeType,
		"sign_type":        wechatV2SignType(firstAny(c, "sign_type", "signType")),
	}
	if values["spbill_create_ip"] == "" {
		values["spbill_create_ip"] = firstAny(c, "client_ip", "spbill_create_ip")
	}
	if values["spbill_create_ip"] == "" {
		values["spbill_create_ip"] = "127.0.0.1"
	}
	if tradeType == "MICROPAY" {
		return a.createMicropay(ctx, client, req, c, values)
	}
	mergeWechatV2Extras(values, req.Extra)
	values["trade_type"] = tradeType
	rsp, err := client.UnifiedOrder(ctx, toGoPayBodyMap(values))
	if err != nil {
		return nil, err
	}
	resultValues, err := wechatV2ResponseValues(rsp)
	if err != nil {
		return nil, err
	}
	if err = validateWechatV2Response(resultValues); err != nil {
		return nil, err
	}
	if err = verifyWechatV2Response(resultValues, client.ApiKey, values["sign_type"]); err != nil {
		return nil, err
	}
	return &biz.PaymentResult{
		Provider: biz.PaymentWechatV2,
		Status:   "created",
		TradeNo:  req.TradeNo,
		Payload:  wechatV2Payload(resultValues),
	}, nil
}

func (a *wechatV2Adapter) createMicropay(ctx context.Context, client *gopayWechat.Client, req *biz.PaymentRequest, c map[string]any, values map[string]string) (*biz.PaymentResult, error) {
	authCode := firstAny(req.Extra, "auth_code", "authcode", "barcode", "pay_code", "payment_code")
	if authCode == "" {
		authCode = firstAny(c, "auth_code", "authcode", "barcode", "pay_code", "payment_code")
	}
	if strings.TrimSpace(authCode) == "" {
		return nil, errors.New("微信支付 v2 付款码支付缺少 auth_code")
	}
	delete(values, "trade_type")
	delete(values, "notify_url")
	mergeWechatV2Extras(values, req.Extra)
	values["auth_code"] = strings.TrimSpace(authCode)
	rsp, err := client.Micropay(ctx, toGoPayBodyMap(values))
	if err != nil {
		return nil, err
	}
	if rsp == nil {
		return nil, errors.New("微信支付 v2 付款码支付响应为空")
	}
	resultValues, err := wechatV2ResponseValues(rsp)
	if err != nil {
		return nil, err
	}
	if err = verifyWechatV2Response(resultValues, client.ApiKey, values["sign_type"]); err != nil {
		return nil, err
	}
	if resultValues["return_code"] != "SUCCESS" {
		return nil, fmt.Errorf("微信支付 v2 付款码支付失败: %s", first(resultValues, "return_msg", "err_code_des", "err_code"))
	}
	if resultValues["result_code"] != "SUCCESS" && !strings.EqualFold(resultValues["err_code"], "USERPAYING") {
		return nil, fmt.Errorf("微信支付 v2 付款码支付失败: %s", first(resultValues, "err_code_des", "return_msg", "err_code"))
	}
	if resultValues["out_trade_no"] == "" {
		return nil, errors.New("微信支付 v2 付款码支付响应缺少 out_trade_no")
	}
	if resultValues["out_trade_no"] != req.TradeNo {
		return nil, errors.New("微信支付 v2 付款码支付响应的 out_trade_no 不匹配")
	}
	if strings.EqualFold(resultValues["err_code"], "USERPAYING") {
		return &biz.PaymentResult{Provider: biz.PaymentWechatV2, Status: "pending", TradeNo: req.TradeNo, Payload: wechatV2Payload(resultValues)}, nil
	}
	providerTradeNo := strings.TrimSpace(resultValues["transaction_id"])
	if providerTradeNo == "" {
		return nil, errors.New("微信支付 v2 付款码支付响应缺少 transaction_id")
	}
	result := &biz.PaymentResult{
		Provider: biz.PaymentWechatV2, Status: "success", TradeNo: req.TradeNo,
		ProviderTradeNo: providerTradeNo, Payload: wechatV2Payload(resultValues),
	}
	amount := strings.TrimSpace(resultValues["total_fee"])
	if amount == "" {
		return nil, errors.New("微信支付 v2 付款码支付响应缺少 total_fee")
	}
	result.Amount, err = parseIntegerAmount(amount)
	if err != nil {
		return nil, fmt.Errorf("解析微信支付 v2 付款码金额: %w", err)
	}
	if result.Amount != req.Amount {
		return nil, errors.New("微信支付 v2 付款码支付响应金额不匹配")
	}
	result.Currency = strings.ToUpper(resultValues["fee_type"])
	requestCurrency := strings.ToUpper(strings.TrimSpace(req.Currency))
	if result.Currency == "" {
		result.Currency = requestCurrency
	} else if requestCurrency != "" && result.Currency != requestCurrency {
		return nil, errors.New("微信支付 v2 付款码支付响应币种不匹配")
	}
	if err = populateWechatV2Breakdown(result, resultValues); err != nil {
		return nil, err
	}
	return result, nil
}

func wechatV2CreateMethod(extra, config map[string]any) (string, error) {
	value := firstAny(extra, "trade_type", "pay_type", "method", "pay_method", "channel")
	if value == "" {
		value = firstAny(config, "trade_type", "pay_type", "method", "pay_method", "channel")
	}
	normalized := strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
	switch normalized {
	case "", "jsapi", "js_api", "mini", "miniapp", "mini_program", "miniprogram", "applet":
		return gopayWechat.TradeType_JsApi, nil
	case "app", "app_pay":
		return gopayWechat.TradeType_App, nil
	case "native", "qr", "qrcode":
		return gopayWechat.TradeType_Native, nil
	case "h5", "h5_pay", "mweb", "wap", "wap_pay":
		return gopayWechat.TradeType_H5, nil
	case "micropay", "micro_pay", "barcode", "barcode_pay", "pay_code", "payment_code":
		return "MICROPAY", nil
	default:
		return "", fmt.Errorf("微信支付 v2 不支持的下单方式: %s", value)
	}
}

func (a *wechatV2Adapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) {
	tradeNo = strings.TrimSpace(tradeNo)
	if tradeNo == "" {
		return nil, errors.New("微信支付 v2 查单缺少 out_trade_no")
	}
	client, err := newWechatV2Client(c, firstAny(c, "query_url", "order_query_url"))
	if err != nil {
		return nil, err
	}
	body := gopay.BodyMap{
		"nonce_str":    nonce(),
		"out_trade_no": tradeNo,
		"sign_type":    wechatV2SignType(firstAny(c, "sign_type", "signType")),
	}
	_, response, err := client.QueryOrder(ctx, body)
	if err != nil {
		return nil, err
	}
	values := fromGoPayBodyMap(response)
	if err = validateWechatV2Response(values); err != nil {
		return nil, err
	}
	if err = verifyWechatV2Response(values, client.ApiKey, body.GetString("sign_type")); err != nil {
		return nil, err
	}
	result := &biz.PaymentResult{
		Provider: biz.PaymentWechatV2,
		Status:   normalizePaymentStatus(values["trade_state"], "pending"),
		TradeNo:  first(values, "out_trade_no"),
		Payload:  wechatV2Payload(values),
	}
	if result.TradeNo == "" {
		result.TradeNo = tradeNo
	} else if result.TradeNo != tradeNo {
		return nil, errors.New("微信支付 v2 查询响应的 out_trade_no 不匹配")
	}
	if result.Status != "success" {
		return result, nil
	}
	result.ProviderTradeNo = values["transaction_id"]
	if result.ProviderTradeNo == "" {
		return nil, errors.New("微信支付 v2 查询响应缺少 transaction_id")
	}
	result.Amount, err = parseIntegerAmount(values["total_fee"])
	if err != nil {
		return nil, fmt.Errorf("解析微信支付 v2 订单金额: %w", err)
	}
	result.Currency = strings.ToUpper(values["fee_type"])
	if result.Currency == "" {
		result.Currency = "CNY"
	}
	if err = populateWechatV2Breakdown(result, values); err != nil {
		return nil, err
	}
	return result, nil
}

func (a *wechatV2Adapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) {
	if req == nil {
		return nil, errors.New("微信支付 v2 退款请求为空")
	}
	tradeNo := strings.TrimSpace(req.TradeNo)
	refundNo := strings.TrimSpace(req.RefundNo)
	if tradeNo == "" || refundNo == "" {
		return nil, errors.New("微信支付 v2 退款缺少 out_trade_no 或 out_refund_no")
	}
	if req.Amount <= 0 || req.TotalAmount <= 0 || req.Amount > req.TotalAmount {
		return nil, errors.New("微信支付 v2 退款金额无效")
	}
	client, err := newWechatV2Client(c, firstAny(c, "refund_url", "refund_apply_url"))
	if err != nil {
		return nil, err
	}
	values := map[string]string{
		"appid":         client.AppId,
		"mch_id":        client.MchId,
		"nonce_str":     nonce(),
		"out_trade_no":  tradeNo,
		"out_refund_no": refundNo,
		"total_fee":     fmt.Sprint(req.TotalAmount),
		"refund_fee":    fmt.Sprint(req.Amount),
		"sign_type":     wechatV2SignType(firstAny(c, "sign_type", "signType")),
	}
	currency := strings.ToUpper(strings.TrimSpace(req.Currency))
	if currency != "" {
		values["refund_fee_type"] = currency
	}
	rsp, response, err := client.Refund(ctx, toGoPayBodyMap(values))
	if err != nil {
		return nil, err
	}
	resultValues := fromGoPayBodyMap(response)
	if rsp == nil {
		return nil, errors.New("微信支付 v2 退款响应为空")
	}
	if err = validateWechatV2Response(resultValues); err != nil {
		return nil, err
	}
	if err = verifyWechatV2Response(resultValues, client.ApiKey, values["sign_type"]); err != nil {
		return nil, err
	}
	if err = validateWechatV2RefundIdentity(tradeNo, refundNo, rsp); err != nil {
		return nil, err
	}
	result := &biz.PaymentResult{
		Provider:        biz.PaymentWechatV2,
		Status:          "created",
		TradeNo:         tradeNo,
		ProviderTradeNo: strings.TrimSpace(rsp.RefundId),
		Currency:        currency,
		Payload:         wechatV2Payload(resultValues),
	}
	if rsp.RefundFee != "" {
		result.Amount, err = parseIntegerAmount(rsp.RefundFee)
		if err != nil {
			return nil, fmt.Errorf("解析微信支付 v2 退款金额: %w", err)
		}
		if result.Amount != req.Amount {
			return nil, errors.New("微信支付 v2 退款响应金额不匹配")
		}
	} else {
		result.Amount = req.Amount
	}
	if responseCurrency := strings.ToUpper(strings.TrimSpace(rsp.FeeType)); responseCurrency != "" {
		if currency != "" && responseCurrency != currency {
			return nil, errors.New("微信支付 v2 退款响应币种不匹配")
		}
		result.Currency = responseCurrency
	}
	return result, nil
}

func validateWechatV2RefundIdentity(tradeNo, refundNo string, rsp *gopayWechat.RefundResponse) error {
	if rsp == nil {
		return errors.New("微信支付 v2 退款响应为空")
	}
	if returnedTradeNo := strings.TrimSpace(rsp.OutTradeNo); returnedTradeNo != "" && returnedTradeNo != tradeNo {
		return errors.New("微信支付 v2 退款响应的 out_trade_no 不匹配")
	}
	if returnedRefundNo := strings.TrimSpace(rsp.OutRefundNo); returnedRefundNo == "" {
		return errors.New("微信支付 v2 退款响应缺少 out_refund_no")
	} else if returnedRefundNo != refundNo {
		return errors.New("微信支付 v2 退款响应的 out_refund_no 不匹配")
	}
	if strings.TrimSpace(rsp.RefundId) == "" {
		return errors.New("微信支付 v2 退款响应缺少 refund_id")
	}
	return nil
}

func (a *wechatV2Adapter) Callback(_ context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) {
	if callback == nil || len(callback.Body) == 0 {
		return nil, errors.New("微信支付 v2 回调为空")
	}
	request, err := http.NewRequest(http.MethodPost, "http://payment-callback.local", bytes.NewReader(callback.Body))
	if err != nil {
		return nil, err
	}
	body, err := gopayWechat.ParseNotifyToBodyMap(request)
	if err != nil {
		return nil, err
	}
	values := fromGoPayBodyMap(body)
	if err = verifyWechatV2(values, firstAny(c, "mch_key", "api_key", "merchant_key")); err != nil {
		return nil, err
	}
	if values["appid"] != firstAny(c, "app_id", "appid") || values["mch_id"] != firstAny(c, "merchant_id", "mch_id") {
		return nil, errors.New("微信支付 v2 回调商户配置不匹配")
	}
	status := "pending"
	if values["return_code"] == "SUCCESS" && values["result_code"] == "SUCCESS" && values["trade_state"] != "REFUND" {
		status = "success"
	}
	payload, _ := json.Marshal(values)
	return &biz.PaymentResult{Provider: biz.PaymentWechatV2, Status: status, TradeNo: first(values, "out_trade_no"), ProviderTradeNo: values["transaction_id"], Payload: payload}, nil
}

func newWechatV2Client(c map[string]any, endpoint string) (*gopayWechat.Client, error) {
	appID := firstAny(c, "app_id", "appid")
	merchantID := firstAny(c, "merchant_id", "mch_id")
	mchKey := firstAny(c, "mch_key", "api_key", "merchant_key")
	if appID == "" || merchantID == "" || mchKey == "" {
		return nil, errors.New("微信支付 v2 缺少 app_id、merchant_id 或 mch_key")
	}

	// The previous adapter sent the configured amount unchanged, including to
	// sandbox or test endpoints. GoPay's sandbox mode rewrites total_fee, so use
	// its production signing flow and redirect the high-level call when a custom
	// endpoint is configured.
	client := gopayWechat.NewClient(appID, merchantID, mchKey, true)
	regular := xhttp.NewClient().SetTimeout(20 * time.Second).SetBodySize(4)
	if err := setWechatV2Endpoint(regular, endpoint); err != nil {
		return nil, err
	}
	client.SetHttpClient(regular)

	tlsHTTPClient, err := paymentHTTPClient(c)
	if err != nil {
		return nil, err
	}
	tlsClient := xhttp.NewClient().SetBodySize(4)
	tlsClient.HttpClient = tlsHTTPClient
	if err = setWechatV2Endpoint(tlsClient, endpoint); err != nil {
		return nil, err
	}
	client.SetTLSHttpClient(tlsClient)
	return client, nil
}

func setWechatV2Endpoint(client *xhttp.Client, endpoint string) error {
	endpoint = strings.TrimSpace(endpoint)
	if endpoint == "" {
		return nil
	}
	target, err := url.Parse(endpoint)
	if err != nil || target.Scheme == "" || target.Host == "" {
		return fmt.Errorf("微信支付 v2 接口地址无效: %s", endpoint)
	}
	next := client.HttpClient.Transport
	if next == nil {
		next = http.DefaultTransport
	}
	client.SetTransport(&wechatV2EndpointTransport{endpoint: target, next: next})
	return nil
}

func wechatV2ResponseValues(value any) (map[string]string, error) {
	raw, err := json.Marshal(value)
	if err != nil {
		return nil, err
	}
	var body gopay.BodyMap
	if err = json.Unmarshal(raw, &body); err != nil {
		return nil, err
	}
	return fromGoPayBodyMap(body), nil
}

func validateWechatV2Response(values map[string]string) error {
	if strings.EqualFold(strings.TrimSpace(values["return_code"]), "SUCCESS") &&
		strings.EqualFold(strings.TrimSpace(values["result_code"]), "SUCCESS") {
		return nil
	}
	message := first(values, "err_code_des", "return_msg", "err_code")
	if message == "" {
		message = "未知错误"
	}
	return fmt.Errorf("微信支付 v2 错误: %s", message)
}

func verifyWechatV2Response(values map[string]string, key, fallbackSignType string) error {
	if values["sign"] == "" {
		return errors.New("微信支付 v2 响应缺少签名")
	}
	signType := values["sign_type"]
	if signType == "" {
		signType = fallbackSignType
	}
	return verifyWechatV2WithSignType(values, key, signType)
}

func wechatV2Payload(values map[string]string) []byte {
	return []byte(gopayWechat.GenerateXml(toGoPayBodyMap(values)))
}

func populateWechatV2Breakdown(result *biz.PaymentResult, values map[string]string) error {
	if result == nil || result.Status != "success" {
		return nil
	}
	// WeChat v2 reports total_fee/cash_fee/coupon_fee in the smallest currency
	// unit. cash_fee is the payer cash amount; coupon_fee is the total coupon
	// discount. v2 does not reliably identify coupon funding parties, so those
	// fields remain zero rather than being misclassified.
	cash, cashOK, err := parseWechatInteger(values, "cash_fee")
	if err != nil {
		return fmt.Errorf("解析微信支付 v2 cash_fee: %w", err)
	}
	if !cashOK {
		return nil
	}
	coupon, couponOK, err := parseWechatInteger(values, "coupon_fee")
	if err != nil {
		return fmt.Errorf("解析微信支付 v2 coupon_fee: %w", err)
	}
	if !couponOK {
		coupon = result.Amount - cash
	}
	if cash < 0 || coupon < 0 || cash > result.Amount || cash+coupon != result.Amount {
		return errors.New("微信支付 v2 总额、现金实付和优惠金额不守恒")
	}
	result.PayerPaidAmount = cash
	result.CashPaidAmount = cash
	result.DiscountAmount = coupon
	if settlement, ok, settlementErr := parseWechatInteger(values, "settlement_total_fee"); settlementErr != nil {
		return fmt.Errorf("解析微信支付 v2 settlement_total_fee: %w", settlementErr)
	} else if ok {
		result.SettlementAmount = settlement
	}
	// coupon_fee_n is a per-coupon detail in v2. Its aggregate still has no
	// authoritative merchant/provider funding split, so preserve the raw list
	// in Payload instead of assigning it to either party.
	_ = couponOK
	result.AmountBreakdownKnown = true
	return nil
}

func parseWechatInteger(values map[string]string, key string) (int64, bool, error) {
	value, exists := values[key]
	if !exists || strings.TrimSpace(value) == "" {
		return 0, false, nil
	}
	amount, err := parseIntegerAmount(value)
	return amount, true, err
}

func mergeWechatV2Extras(dst map[string]string, extra map[string]any) {
	protected := map[string]struct{}{
		"appid": {}, "mch_id": {}, "nonce_str": {}, "body": {}, "out_trade_no": {},
		"total_fee": {}, "fee_type": {}, "spbill_create_ip": {}, "notify_url": {}, "trade_type": {},
		"auth_code": {}, "sign": {},
	}
	for key, value := range stringMap(extra) {
		if _, exists := protected[key]; exists {
			continue
		}
		dst[key] = value
	}
}
func verifyWechatV2(values map[string]string, key string) error {
	return verifyWechatV2WithSignType(values, key, values["sign_type"])
}

func verifyWechatV2WithSignType(values map[string]string, key, signType string) error {
	if key == "" {
		return errors.New("微信支付 v2 未配置 mch_key")
	}
	ok, err := gopayWechat.VerifySign(key, wechatV2SignType(signType), cloneGoPayBodyMap(toGoPayBodyMap(values)))
	if err != nil {
		return err
	}
	if !ok {
		return errors.New("微信支付 v2 回调签名校验失败")
	}
	return nil
}

func paymentHTTPClient(c map[string]any) (*http.Client, error) {
	certPEM := firstAny(c, "client_cert", "cert_pem", "apiclient_cert")
	keyPEM := firstAny(c, "client_key", "key_pem", "apiclient_key")
	if certPEM == "" && keyPEM == "" {
		return &http.Client{Timeout: 20 * time.Second}, nil
	}
	if certPEM == "" || keyPEM == "" {
		return nil, errors.New("微信支付 v2 client_cert 和 client_key 必须同时配置")
	}
	cert, err := tls.X509KeyPair([]byte(certPEM), []byte(keyPEM))
	if err != nil {
		return nil, err
	}
	var pool *x509.CertPool
	if ca := firstAny(c, "ca_cert", "root_cert"); ca != "" {
		pool = x509.NewCertPool()
		if !pool.AppendCertsFromPEM([]byte(ca)) {
			return nil, errors.New("微信支付 v2 ca_cert 格式错误")
		}
	}
	return &http.Client{Timeout: 20 * time.Second, Transport: &http.Transport{TLSClientConfig: &tls.Config{Certificates: []tls.Certificate{cert}, RootCAs: pool, MinVersion: tls.VersionTLS12}}}, nil
}

func nonce() string { return fmt.Sprintf("%d", time.Now().UnixNano()) }
func stringOr(extra map[string]any, key, fallback string) string {
	if value, ok := extra[key].(string); ok {
		return value
	}
	return fallback
}
func stringMap(values map[string]any) map[string]string {
	result := map[string]string{}
	for key, value := range values {
		if text, ok := value.(string); ok {
			result[key] = text
		}
	}
	return result
}
