package payment

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

	"github.com/go-pay/gopay"
	"github.com/go-pay/gopay/saobei"
)

type saobeiClient interface {
	MiniPay(context.Context, gopay.BodyMap) (*saobei.MiniPayRsp, error)
	BarcodePay(context.Context, gopay.BodyMap) (*saobei.BarcodePayRsp, error)
	Query(context.Context, gopay.BodyMap) (*saobei.QueryRsp, error)
	Refund(context.Context, gopay.BodyMap) (*saobei.RefundRsp, error)
}

type saobeiAdapter struct {
	newClient func(map[string]any) (saobeiClient, error)
}

func saobeiBarcodeOK(returnCode, resultCode string) bool {
	return saobeiResultOK(returnCode, resultCode)
}

func (a *saobeiAdapter) client(c map[string]any) (saobeiClient, error) {
	if a.newClient != nil {
		return a.newClient(c)
	}
	return saobei.NewClient(text(c, "inst_no"), text(c, "key"), text(c, "merchant_no"), text(c, "terminal_id"), text(c, "access_token"), !strings.EqualFold(text(c, "environment"), "sandbox"))
}

func (a *saobeiAdapter) Create(ctx context.Context, req *bizpayment.PaymentRequest, c map[string]any) (*bizpayment.PaymentResult, error) {
	if req == nil {
		return nil, errors.New("扫呗支付下单请求为空")
	}
	method, err := saobeiCreateMethod(req.Extra, c)
	if err != nil {
		return nil, err
	}
	client, err := a.client(c)
	if err != nil {
		return nil, err
	}
	now := time.Now().Format("20060102150405")
	bm := gopay.BodyMap{"pay_type": firstAnyOr(req.Extra, "010", "pay_type", "payment_type"), "terminal_ip": req.ClientIP, "terminal_trace": req.TradeNo, "terminal_time": now, "total_fee": req.Amount}
	mergeGoPayExtras(bm, req.Extra, "pay_type", "payment_type", "terminal_ip", "terminal_trace", "terminal_time", "total_fee", "method", "pay_method", "trade_type")
	if method == "barcode" {
		authNo := strings.TrimSpace(firstAny(req.Extra, "auth_no", "auth_code", "authcode", "barcode", "pay_code"))
		if authNo == "" {
			return nil, errors.New("扫呗付款码支付缺少 auth_no")
		}
		bm.Set("auth_no", authNo)
		rsp, callErr := client.BarcodePay(ctx, bm)
		if callErr != nil {
			return nil, callErr
		}
		if rsp == nil || !saobeiBarcodeOK(rsp.ReturnCode, rsp.ResultCode) {
			return nil, fmt.Errorf("扫呗付款码支付失败: %s", firstNonEmpty(rspMap(rsp)["return_msg"], rspMap(rsp)["result_code"]))
		}
		if strings.TrimSpace(rsp.OutTradeNo) == "" {
			return nil, errors.New("扫呗支付下单响应缺少 out_trade_no")
		}
		status := "created"
		if strings.TrimSpace(rsp.ResultCode) == saobei.ResultCodePaying {
			status = "pending"
		}
		return &bizpayment.PaymentResult{Provider: bizpayment.PaymentSaobei, Status: status, TradeNo: req.TradeNo, QueryID: strings.TrimSpace(rsp.OutTradeNo), Payload: mustJSON(rsp)}, nil
	}
	rsp, err := client.MiniPay(ctx, bm)
	if err != nil {
		return nil, err
	}
	if rsp == nil || !saobeiOK(rsp.ReturnCode, rsp.ResultCode) {
		return nil, fmt.Errorf("扫呗支付下单失败: %s", firstNonEmpty(rspMap(rsp)["return_msg"], rspMap(rsp)["result_code"]))
	}
	if strings.TrimSpace(rsp.OutTradeNo) == "" {
		return nil, errors.New("扫呗支付下单响应缺少 out_trade_no")
	}
	return &bizpayment.PaymentResult{Provider: bizpayment.PaymentSaobei, Status: "client_pending", TradeNo: req.TradeNo, QueryID: strings.TrimSpace(rsp.OutTradeNo), Payload: mustJSON(rsp)}, nil
}

func saobeiCreateMethod(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 "", "mini", "mini_pay", "miniapp", "mini_app", "mini_program", "miniprogram", "jsapi", "js_api":
		return "mini", nil
	case "barcode", "barcode_pay", "micropay", "micro_pay", "scan", "scan_pay", "pay_code", "payment_code":
		return "barcode", nil
	default:
		return "", fmt.Errorf("扫呗支付不支持的下单方式: %s", value)
	}
}

func (a *saobeiAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*bizpayment.PaymentResult, error) {
	tradeNo = strings.TrimSpace(tradeNo)
	if tradeNo == "" {
		return nil, errors.New("扫呗支付查单缺少 out_trade_no")
	}
	client, err := a.client(c)
	if err != nil {
		return nil, err
	}
	// terminal_trace is the query request's own trace ID. The durable platform
	// order identity is carried by out_trade_no and comes from PaymentResult.QueryID.
	bm := gopay.BodyMap{"pay_type": firstAnyOr(c, "010", "pay_type", "payment_type"), "terminal_trace": nonce(), "terminal_time": time.Now().Format("20060102150405"), "out_trade_no": tradeNo}
	mergeGoPayConfigExtras(bm, c, "query_extra", "pay_type", "terminal_trace", "terminal_time", "out_trade_no")
	rsp, err := client.Query(ctx, bm)
	if err != nil {
		return nil, err
	}
	if rsp == nil {
		return nil, errors.New("扫呗支付查单响应为空")
	}
	if !saobeiResultOK(rsp.ReturnCode, rsp.ResultCode) {
		return nil, fmt.Errorf("扫呗支付查单失败: %s", firstNonEmpty(rspMap(rsp)["return_msg"], rspMap(rsp)["result_code"]))
	}
	queryID := strings.TrimSpace(tradeNo)
	if responseQueryID := strings.TrimSpace(rsp.OutTradeNo); responseQueryID == "" {
		return nil, errors.New("扫呗支付查单响应缺少 out_trade_no")
	} else if responseQueryID != queryID {
		return nil, errors.New("扫呗支付查单平台订单号不匹配")
	}
	tradeNoResult := strings.TrimSpace(rsp.TerminalTrace)
	if tradeNoResult == "" {
		tradeNoResult = strings.TrimSpace(rsp.PayTrace)
	}
	if tradeNoResult == "" {
		return nil, errors.New("扫呗支付查单响应缺少商户订单号")
	}
	status := normalizeSaobeiState(rsp.TradeState)
	if strings.TrimSpace(rsp.ResultCode) == saobei.ResultCodePaying {
		status = "pending"
	}
	result := &bizpayment.PaymentResult{Provider: bizpayment.PaymentSaobei, Status: status, TradeNo: tradeNoResult, ProviderTradeNo: strings.TrimSpace(rsp.ChannelTradeNo), QueryID: queryID, Payload: mustJSON(rsp), Currency: strings.ToUpper(firstAnyOr(c, "CNY", "currency", "default_currency"))}
	if rsp.TotalFee != "" {
		result.Amount, err = parseIntegerAmount(rsp.TotalFee)
		if err != nil {
			return nil, fmt.Errorf("解析扫呗订单金额: %w", err)
		}
	}
	if rsp.BuyerPayFee != "" {
		result.PayerPaidAmount, err = parseIntegerAmount(rsp.BuyerPayFee)
		if err != nil {
			return nil, fmt.Errorf("解析扫呗买家实付金额: %w", err)
		}
		result.CashPaidAmount = result.PayerPaidAmount
		if result.Amount >= result.PayerPaidAmount {
			result.DiscountAmount = result.Amount - result.PayerPaidAmount
			result.AmountBreakdownKnown = true
		}
	}
	return result, err
}

func (a *saobeiAdapter) Refund(ctx context.Context, req *bizpayment.PaymentRefundRequest, c map[string]any) (*bizpayment.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("扫呗支付退款缺少商户订单号或退款单号")
	}
	if req.Amount <= 0 {
		return nil, errors.New("扫呗支付退款金额无效")
	}
	client, err := a.client(c)
	if err != nil {
		return nil, err
	}
	outTradeNo := strings.TrimSpace(req.QueryID)
	if outTradeNo == "" {
		outTradeNo = tradeNo
	}
	bm := gopay.BodyMap{"pay_type": firstAnyOr(c, "010", "pay_type", "payment_type"), "terminal_trace": refundNo, "terminal_time": time.Now().Format("20060102150405"), "refund_fee": req.Amount, "out_trade_no": outTradeNo}
	mergeGoPayConfigExtras(bm, c, "refund_extra", "pay_type", "terminal_trace", "terminal_time", "refund_fee", "out_trade_no", "out_refund_no")
	rsp, err := client.Refund(ctx, bm)
	if err != nil {
		return nil, err
	}
	if rsp == nil {
		return nil, errors.New("扫呗支付退款响应为空")
	}
	if !saobeiResultOK(rsp.ReturnCode, rsp.ResultCode) {
		return nil, fmt.Errorf("扫呗支付退款失败: %s", firstNonEmpty(rspMap(rsp)["return_msg"], rspMap(rsp)["result_code"]))
	}
	if responseOrderID := strings.TrimSpace(rsp.OutTradeNo); responseOrderID != "" && responseOrderID != outTradeNo {
		return nil, errors.New("扫呗支付退款响应平台订单号不匹配")
	}
	returnedTrace := strings.TrimSpace(rsp.TerminalTrace)
	if returnedTrace == "" {
		return nil, errors.New("扫呗支付退款响应缺少 terminal_trace")
	}
	if returnedTrace != refundNo {
		return nil, errors.New("扫呗支付退款响应的 terminal_trace 不匹配")
	}
	providerRefundID := strings.TrimSpace(rsp.OutRefundNo)
	if providerRefundID == "" {
		return nil, errors.New("扫呗支付退款响应缺少 out_refund_no")
	}
	refundAmount, err := parseIntegerAmount(rsp.RefundFee)
	if err != nil {
		return nil, fmt.Errorf("解析扫呗退款金额: %w", err)
	}
	if refundAmount != req.Amount {
		return nil, errors.New("扫呗支付退款响应金额不匹配")
	}
	return &bizpayment.PaymentResult{Provider: bizpayment.PaymentSaobei, Status: "pending", TradeNo: tradeNo, ProviderTradeNo: providerRefundID, Amount: refundAmount, Currency: strings.ToUpper(req.Currency), Payload: mustJSON(rsp)}, nil
}

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