package payment

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	bizpayment "kra/internal/biz/payment"
	"strings"

	"github.com/go-pay/gopay"
	gopayQQ "github.com/go-pay/gopay/qq"
)

type qqAdapter struct{}

func (a *qqAdapter) client(c map[string]any) (*gopayQQ.Client, error) {
	mch := firstAny(c, "merchant_id", "mch_id")
	if mch == "" || text(c, "api_key") == "" {
		return nil, errors.New("QQ 支付缺少 merchant_id 或 api_key")
	}
	client := gopayQQ.NewClient(mch, text(c, "api_key"))
	return client, nil
}

// qqSignType resolves the algorithm used both for request signing and for
// validating synchronous API responses. QQ defaults to MD5 when sign_type is
// omitted, but an explicit unsupported value must never silently downgrade to
// a weaker or different algorithm.
func qqSignType(extra, config map[string]any) (string, error) {
	value := firstAny(extra, "sign_type", "signature_type", "signType")
	if value == "" {
		value = firstAny(config, "sign_type", "signature_type", "signType")
	}
	if value == "" {
		return gopayQQ.SignType_MD5, nil
	}
	normalized := strings.ToUpper(strings.ReplaceAll(strings.TrimSpace(value), "_", "-"))
	switch normalized {
	case gopayQQ.SignType_MD5:
		return gopayQQ.SignType_MD5, nil
	case gopayQQ.SignType_HMAC_SHA256:
		return gopayQQ.SignType_HMAC_SHA256, nil
	default:
		return "", fmt.Errorf("QQ 支付不支持的签名类型: %s", value)
	}
}

func verifyQQResponse(apiKey, signType string, response any) error {
	if strings.TrimSpace(apiKey) == "" {
		return errors.New("QQ 支付响应验签缺少 api_key")
	}
	ok, err := gopayQQ.VerifySign(apiKey, signType, response)
	if err != nil {
		return fmt.Errorf("QQ 支付响应签名校验失败: %w", err)
	}
	if !ok {
		return errors.New("QQ 支付响应签名校验失败")
	}
	return nil
}

func (a *qqAdapter) Create(ctx context.Context, req *bizpayment.PaymentRequest, c map[string]any) (*bizpayment.PaymentResult, error) {
	if req == nil {
		return nil, errors.New("QQ 支付下单请求为空")
	}
	client, err := a.client(c)
	if err != nil {
		return nil, err
	}
	signType, err := qqSignType(req.Extra, c)
	if err != nil {
		return nil, err
	}
	bm := gopay.BodyMap{}
	bm.Set("nonce_str", nonce()).Set("body", req.Subject).Set("out_trade_no", req.TradeNo).Set("total_fee", req.Amount).Set("spbill_create_ip", req.ClientIP).Set("notify_url", req.NotifyURL).Set("sign_type", signType)
	tradeType, err := qqCreateMethod(req.Extra, c)
	if err != nil {
		return nil, err
	}
	if tradeType == gopayQQ.TradeType_MicroPay {
		bm.Set("device_info", firstAnyOr(req.Extra, firstAnyOr(c, "WEB", "device_info"), "device_info"))
		authCode := firstAny(req.Extra, "auth_code", "authcode", "barcode", "pay_code")
		if strings.TrimSpace(authCode) == "" {
			return nil, errors.New("QQ 付款码支付缺少 auth_code")
		}
		bm.Set("auth_code", strings.TrimSpace(authCode))
		mergeGoPayExtras(bm, req.Extra, "nonce_str", "body", "out_trade_no", "total_fee", "spbill_create_ip", "notify_url", "trade_type", "device_info", "auth_code", "authcode", "mch_id", "sign_type")
		rsp, callErr := client.MicroPay(ctx, bm)
		if callErr != nil {
			return nil, callErr
		}
		if callErr = verifyQQResponse(text(c, "api_key"), signType, rsp); callErr != nil {
			return nil, callErr
		}
		if rsp == nil || !qqOK(rsp.ReturnCode, rsp.ResultCode) {
			return nil, fmt.Errorf("QQ 付款码支付失败: %s", first(rspMap(rsp), "return_msg", "ret_msg", "err_code_des"))
		}
		if returnedTradeNo := strings.TrimSpace(rsp.OutTradeNo); returnedTradeNo != "" && returnedTradeNo != req.TradeNo {
			return nil, errors.New("QQ 付款码支付响应的 out_trade_no 不匹配")
		}
		status := normalizeQQState(rsp.TradeState)
		if status == "pending" && strings.EqualFold(rsp.ResultCode, "SUCCESS") && strings.TrimSpace(rsp.TransactionId) != "" {
			status = "success"
		}
		return &bizpayment.PaymentResult{Provider: bizpayment.PaymentQQ, Status: status, TradeNo: firstNonEmptyQQ(rsp.OutTradeNo, req.TradeNo), ProviderTradeNo: strings.TrimSpace(rsp.TransactionId), Payload: mustJSON(rsp)}, nil
	}
	bm.Set("trade_type", tradeType)
	mergeGoPayExtras(bm, req.Extra, "nonce_str", "body", "out_trade_no", "total_fee", "spbill_create_ip", "notify_url", "trade_type", "mch_id", "sign_type")
	rsp, err := client.UnifiedOrder(ctx, bm)
	if err != nil {
		return nil, err
	}
	if err = verifyQQResponse(text(c, "api_key"), signType, rsp); err != nil {
		return nil, err
	}
	if rsp == nil || !qqOK(rsp.ReturnCode, rsp.ResultCode) {
		return nil, fmt.Errorf("QQ 支付下单失败: %s", first(rspMap(rsp), "return_msg", "ret_msg", "err_code_des"))
	}
	result := &bizpayment.PaymentResult{Provider: bizpayment.PaymentQQ, Status: "created", TradeNo: req.TradeNo, Payload: mustJSON(rsp)}
	return result, nil
}

func qqCreateMethod(extra, config map[string]any) (string, error) {
	value := firstAny(extra, "trade_type", "pay_type", "method", "pay_method")
	if value == "" {
		value = firstAny(config, "trade_type", "pay_type", "method", "pay_method")
	}
	normalized := strings.ToUpper(strings.ReplaceAll(strings.TrimSpace(value), "-", "_"))
	switch normalized {
	case "", "NATIVE", "QR", "QRCODE":
		return gopayQQ.TradeType_Native, nil
	case "MICROPAY", "MICRO_PAY", "BARCODE", "BARCODE_PAY":
		return gopayQQ.TradeType_MicroPay, nil
	case "JSAPI", "JS_API":
		return gopayQQ.TradeType_JsApi, nil
	case "MINIAPP", "MINI_APP", "MINI_PROGRAM", "MINIPROGRAM", "MINI":
		return gopayQQ.TradeType_Mini, nil
	case "APP", "APP_PAY":
		return gopayQQ.TradeType_App, nil
	default:
		return "", fmt.Errorf("QQ 支付不支持的下单方式: %s", value)
	}
}

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

func (a *qqAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*bizpayment.PaymentResult, error) {
	tradeNo = strings.TrimSpace(tradeNo)
	if tradeNo == "" {
		return nil, errors.New("QQ 支付查单缺少 out_trade_no")
	}
	client, err := a.client(c)
	if err != nil {
		return nil, err
	}
	signType, err := qqSignType(nil, c)
	if err != nil {
		return nil, err
	}
	bm := gopay.BodyMap{"nonce_str": nonce(), "out_trade_no": tradeNo, "sign_type": signType}
	mergeGoPayConfigExtras(bm, c, "query_extra", "nonce_str", "out_trade_no", "mch_id", "sign_type")
	rsp, err := client.OrderQuery(ctx, bm)
	if err != nil {
		return nil, err
	}
	if err = verifyQQResponse(text(c, "api_key"), signType, rsp); err != nil {
		return nil, err
	}
	if rsp == nil || !qqOK(rsp.ReturnCode, rsp.ResultCode) {
		return nil, fmt.Errorf("QQ 支付查单失败: %s", first(rspMap(rsp), "return_msg", "ret_msg", "err_code_des"))
	}
	result := &bizpayment.PaymentResult{Provider: bizpayment.PaymentQQ, Status: normalizeQQState(rsp.TradeState), TradeNo: rsp.OutTradeNo, ProviderTradeNo: rsp.TransactionId, Payload: mustJSON(rsp)}
	if result.TradeNo == "" {
		result.TradeNo = tradeNo
	} else if strings.TrimSpace(result.TradeNo) != tradeNo {
		return nil, errors.New("QQ 支付查单响应的 out_trade_no 不匹配")
	}
	result.Currency = strings.ToUpper(rsp.FeeType)
	if result.Currency == "" {
		result.Currency = "CNY"
	}
	if rsp.TotalFee != "" {
		result.Amount, err = parseIntegerAmount(rsp.TotalFee)
		if err != nil {
			return nil, err
		}
	}
	if rsp.CashFee != "" {
		result.PayerPaidAmount, err = parseIntegerAmount(rsp.CashFee)
		if err != nil {
			return nil, fmt.Errorf("解析 QQ 支付实付金额: %w", err)
		}
		result.CashPaidAmount = result.PayerPaidAmount
		result.AmountBreakdownKnown = true
	}
	return result, nil
}

func (a *qqAdapter) Refund(ctx context.Context, req *bizpayment.PaymentRefundRequest, c map[string]any) (*bizpayment.PaymentResult, error) {
	if req == nil {
		return nil, errors.New("QQ 支付退款请求为空")
	}
	tradeNo := strings.TrimSpace(req.TradeNo)
	refundNo := strings.TrimSpace(req.RefundNo)
	if tradeNo == "" || refundNo == "" {
		return nil, errors.New("QQ 支付退款缺少 out_trade_no 或 out_refund_no")
	}
	if req.Amount <= 0 || req.TotalAmount < 0 || req.TotalAmount > 0 && req.Amount > req.TotalAmount {
		return nil, errors.New("QQ 支付退款金额无效")
	}
	client, err := a.client(c)
	if err != nil {
		return nil, err
	}
	signType, err := qqSignType(nil, c)
	if err != nil {
		return nil, err
	}
	bm := gopay.BodyMap{"nonce_str": nonce(), "out_trade_no": tradeNo, "out_refund_no": refundNo, "refund_fee": req.Amount, "op_user_id": firstAny(c, "op_user_id", "merchant_id", "mch_id"), "op_user_passwd": text(c, "op_user_passwd"), "sign_type": signType}
	if req.TotalAmount > 0 {
		bm.Set("total_fee", req.TotalAmount)
	}
	if req.Currency != "" {
		bm.Set("refund_fee_type", strings.ToUpper(req.Currency))
	}
	mergeGoPayConfigExtras(bm, c, "refund_extra", "nonce_str", "out_trade_no", "out_refund_no", "refund_fee", "total_fee", "op_user_id", "op_user_passwd", "mch_id", "sign_type")
	cert, key, p12 := anyConfig(c, "cert_file", "cert_path"), anyConfig(c, "key_file", "key_path"), anyConfig(c, "pkcs12_file", "p12_file")
	if (cert == nil || key == nil) && p12 == nil {
		if text(c, "cert_content") != "" || text(c, "key_content") != "" || text(c, "pkcs12_content") != "" {
			cert, key, p12 = []byte(text(c, "cert_content")), []byte(text(c, "key_content")), []byte(text(c, "pkcs12_content"))
		}
	}
	if (cert == nil || key == nil) && p12 == nil {
		return nil, errors.New("QQ 支付退款缺少 cert_file、key_file、pkcs12_file（或 *_content）")
	}
	rsp, err := client.Refund(ctx, bm, cert, key, p12)
	if err != nil {
		return nil, err
	}
	if err = verifyQQResponse(text(c, "api_key"), signType, rsp); err != nil {
		return nil, err
	}
	if rsp == nil || !qqOK(rsp.ReturnCode, rsp.ResultCode) {
		return nil, fmt.Errorf("QQ 支付退款失败: %s", first(rspMap(rsp), "return_msg", "ret_msg", "err_code_des"))
	}
	if err = validateQQRefundIdentity(tradeNo, refundNo, rsp); err != nil {
		return nil, err
	}
	refundAmount := req.Amount
	if strings.TrimSpace(rsp.RefundFee) != "" {
		refundAmount, err = parseIntegerAmount(rsp.RefundFee)
		if err != nil {
			return nil, fmt.Errorf("解析 QQ 支付退款金额: %w", err)
		}
		if refundAmount != req.Amount {
			return nil, errors.New("QQ 支付退款响应金额不匹配")
		}
	}
	returnedTradeNo := strings.TrimSpace(rsp.OutTradeNo)
	if returnedTradeNo == "" {
		returnedTradeNo = tradeNo
	}
	return &bizpayment.PaymentResult{Provider: bizpayment.PaymentQQ, Status: "pending", TradeNo: returnedTradeNo, ProviderTradeNo: strings.TrimSpace(rsp.RefundId), Amount: refundAmount, Currency: strings.ToUpper(req.Currency), Payload: mustJSON(rsp)}, nil
}

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

func (a *qqAdapter) Callback(_ context.Context, callback *bizpayment.PaymentCallback, c map[string]any) (*bizpayment.PaymentResult, error) {
	if callback == nil {
		return nil, errors.New("QQ 支付回调为空")
	}
	request, err := callbackRequest(callback)
	if err != nil {
		return nil, err
	}
	bm, err := gopayQQ.ParseNotifyToBodyMap(request)
	if err != nil {
		return nil, err
	}
	signType, err := qqSignType(nil, c)
	if err != nil {
		return nil, err
	}
	ok, err := gopayQQ.VerifySign(text(c, "api_key"), signType, cloneGoPayBodyMap(bm))
	if err != nil {
		return nil, err
	}
	if !ok {
		return nil, errors.New("QQ 支付回调签名校验失败")
	}
	values := fromGoPayBodyMap(bm)
	status := "pending"
	if strings.EqualFold(values["return_code"], "SUCCESS") && strings.EqualFold(values["result_code"], "SUCCESS") && strings.EqualFold(values["trade_state"], "SUCCESS") {
		status = "success"
	}
	payload, _ := json.Marshal(values)
	return &bizpayment.PaymentResult{Provider: bizpayment.PaymentQQ, Status: status, TradeNo: values["out_trade_no"], ProviderTradeNo: values["transaction_id"], Payload: payload}, nil
}
