package payment

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"kra/internal/biz"

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

type lakalaAdapter struct{}

func (a *lakalaAdapter) client(c map[string]any) (*lakala.Client, error) {
	return lakala.NewClient(text(c, "partner_code"), text(c, "credential_code"), !strings.EqualFold(text(c, "environment"), "sandbox"))
}

func (a *lakalaAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) {
	if req == nil {
		return nil, errors.New("拉卡拉支付下单请求为空")
	}
	client, err := a.client(c)
	if err != nil {
		return nil, err
	}
	bm := gopay.BodyMap{"description": req.Subject, "price": req.Amount, "channel": firstAny(req.Extra, "channel", "pay_channel")}
	if bm.GetString("channel") == gopay.NULL {
		bm.Set("channel", firstAnyOr(c, "Wechat", "channel", "pay_channel"))
	}
	mergeGoPayExtras(bm, req.Extra, "description", "price", "channel", "pay_channel", "method", "pay_method", "trade_type")
	method := lakalaCreateMethod(req.Extra, c)
	var rsp *lakala.PaymentRsp
	switch method {
	case "", "jsapi", "js_api", "jsapi_order":
		rsp, err = client.CreateJSAPIOrder(ctx, req.TradeNo, bm)
	case "h5", "h5_pay", "mobile_h5", "mobile_h5_pay":
		rsp, err = client.CreateH5PayOrder(ctx, req.TradeNo, bm)
	case "mini", "miniapp", "mini_program", "miniprogram", "microapp":
		rsp, err = client.CreateMiniProgramOrder(ctx, req.TradeNo, bm)
	case "native", "native_qrcode", "native_qr":
		rsp, err = client.CreateNativeQRCodeOrder(ctx, req.TradeNo, bm)
	case "qrcode", "qr", "qr_code":
		rsp, err = client.CreateQRCodeOrder(ctx, req.TradeNo, bm)
	case "native_jsapi", "native_js_api", "offline_jsapi", "offline_js_api":
		rsp, err = client.CreateNativeJSApiOrder(ctx, req.TradeNo, bm)
	case "sdk", "sdk_payment", "app", "app_pay":
		rsp, err = client.CreateSDKPaymentOrder(ctx, req.TradeNo, bm)
	case "web", "web_gateway", "gateway":
		rsp, err = client.CreateWebGatewayOrder(ctx, req.TradeNo, bm)
	case "retail", "retail_pay", "micropay", "barcode":
		retailRsp, retailErr := client.CreateRetailOrder(ctx, req.TradeNo, bm)
		if retailErr != nil {
			return nil, retailErr
		}
		return lakalaRetailCreateResult(req.TradeNo, retailRsp)
	case "retail_qrcode", "retail_qr", "retail_qr_code":
		rsp, err = client.CreateRetailQRCodeOrder(ctx, req.TradeNo, bm)
	default:
		return nil, fmt.Errorf("拉卡拉支付不支持的下单方式: %s", method)
	}
	if err != nil {
		return nil, err
	}
	return lakalaCreateResult(req.TradeNo, rsp)
}

func lakalaCreateMethod(extra, config map[string]any) string {
	method := firstAny(extra, "method", "pay_method", "trade_type")
	if method == "" {
		method = firstAny(config, "method", "pay_method", "trade_type")
	}
	return strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(method)))
}

func lakalaCreateResult(tradeNo string, rsp *lakala.PaymentRsp) (*biz.PaymentResult, error) {
	if rsp == nil || !lakalaCreateResponseOK(rsp.ReturnCode, rsp.ResultCode) {
		return nil, fmt.Errorf("拉卡拉支付下单失败: %v", rsp)
	}
	orderID := strings.TrimSpace(rsp.OrderId)
	if orderID == "" {
		return nil, errors.New("拉卡拉支付下单响应缺少 order_id")
	}
	if partnerOrderID := strings.TrimSpace(rsp.PartnerOrderId); partnerOrderID != "" && partnerOrderID != tradeNo {
		return nil, errors.New("拉卡拉支付下单响应的商户订单号不匹配")
	}
	return &biz.PaymentResult{Provider: biz.PaymentLakala, Status: "created", TradeNo: tradeNo, QueryID: orderID, Payload: mustJSON(rsp)}, nil
}

func lakalaRetailCreateResult(tradeNo string, rsp *lakala.RetailPayRsp) (*biz.PaymentResult, error) {
	if rsp == nil || !lakalaRetailCreateResponseOK(rsp.ReturnCode, rsp.ResultCode) {
		return nil, fmt.Errorf("拉卡拉线下支付下单失败: %v", rsp)
	}
	orderID := strings.TrimSpace(rsp.OrderId)
	if orderID == "" {
		return nil, errors.New("拉卡拉线下支付下单响应缺少 order_id")
	}
	if partnerOrderID := strings.TrimSpace(rsp.PartnerOrderId); partnerOrderID != "" && partnerOrderID != tradeNo {
		return nil, errors.New("拉卡拉线下支付下单响应的商户订单号不匹配")
	}
	result := &biz.PaymentResult{
		Provider: biz.PaymentLakala, Status: "created", TradeNo: tradeNo,
		QueryID: orderID, Amount: int64(rsp.TotalFee), Currency: strings.ToUpper(rsp.Currency),
		Payload: mustJSON(rsp),
	}
	return result, nil
}

func (a *lakalaAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) {
	client, err := a.client(c)
	if err != nil {
		return nil, err
	}
	orderID := strings.TrimSpace(tradeNo)
	if orderID == "" {
		return nil, errors.New("拉卡拉支付查单缺少 order_id")
	}
	rsp, err := client.OrderStatus(ctx, orderID)
	if err != nil {
		return nil, err
	}
	if rsp == nil || !lakalaQueryResponseOK(rsp.ReturnCode, rsp.ResultCode) {
		return nil, fmt.Errorf("拉卡拉支付查单失败: %v", rsp)
	}
	if responseOrderID := strings.TrimSpace(rsp.OrderId); responseOrderID != "" && responseOrderID != orderID {
		return nil, errors.New("拉卡拉支付查单响应的 order_id 不匹配")
	}
	status := normalizeLakalaState(first(rspMap(rsp), "result_code", "status", "channel_error_code"))
	returnedTradeNo := strings.TrimSpace(rsp.PartnerOrderId)
	if returnedTradeNo == "" {
		return nil, errors.New("拉卡拉支付查单响应缺少商户订单号")
	}
	result := &biz.PaymentResult{Provider: biz.PaymentLakala, Status: status, TradeNo: returnedTradeNo, ProviderTradeNo: strings.TrimSpace(rsp.ChannelOrderId), QueryID: orderID, Amount: int64(rsp.TotalFee), Currency: strings.ToUpper(rsp.Currency), Payload: mustJSON(rsp)}
	if result.Currency == "" {
		result.Currency = strings.ToUpper(firstAny(c, "currency", "default_currency"))
	}
	if result.Currency == "" {
		result.Currency = "JPY"
	}
	if rsp.RealFee > 0 {
		result.PayerPaidAmount = int64(rsp.RealFee)
		result.CashPaidAmount = result.PayerPaidAmount
		result.DiscountAmount = result.Amount - result.PayerPaidAmount
		if result.DiscountAmount >= 0 {
			result.AmountBreakdownKnown = true
		}
	}
	return result, nil
}

func (a *lakalaAdapter) 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("拉卡拉支付退款缺少商户订单号、退款单号或有效金额")
	}
	client, err := a.client(c)
	if err != nil {
		return nil, err
	}
	orderID := strings.TrimSpace(req.QueryID)
	if orderID == "" {
		orderID = tradeNo
	}
	bm := gopay.BodyMap{"fee": req.Amount}
	if req.Currency != "" {
		bm.Set("currency", strings.ToUpper(req.Currency))
	}
	mergeGoPayConfigExtras(bm, c, "refund_extra", "fee", "currency")
	rsp, err := client.ApplyRefund(ctx, orderID, refundNo, bm)
	if err != nil {
		return nil, err
	}
	effective := *req
	effective.TradeNo = tradeNo
	effective.RefundNo = refundNo
	return lakalaRefundResult(&effective, rsp)
}

func lakalaRefundResult(req *biz.PaymentRefundRequest, rsp *lakala.RefundRsp) (*biz.PaymentResult, error) {
	if rsp == nil || !lakalaRefundResponseOK(rsp.ReturnCode, rsp.ResultCode) {
		return nil, fmt.Errorf("拉卡拉支付退款失败: %v", rsp)
	}
	if partnerRefundID := strings.TrimSpace(rsp.PartnerRefundId); partnerRefundID == "" {
		return nil, errors.New("拉卡拉支付退款响应缺少 partner_refund_id")
	} else if partnerRefundID != req.RefundNo {
		return nil, errors.New("拉卡拉支付退款响应的 partner_refund_id 不匹配")
	}
	providerRefundID := strings.TrimSpace(rsp.RefundId)
	if providerRefundID == "" {
		return nil, errors.New("拉卡拉支付退款响应缺少 refund_id")
	}
	if int64(rsp.Amount) != req.Amount {
		return nil, errors.New("拉卡拉支付退款响应金额不匹配")
	}
	currency := strings.ToUpper(strings.TrimSpace(rsp.Currency))
	requestCurrency := strings.ToUpper(strings.TrimSpace(req.Currency))
	if currency == "" {
		currency = requestCurrency
	} else if requestCurrency != "" && currency != requestCurrency {
		return nil, errors.New("拉卡拉支付退款响应币种不匹配")
	}
	return &biz.PaymentResult{Provider: biz.PaymentLakala, Status: normalizeLakalaRefundState(rsp.ResultCode), TradeNo: req.TradeNo, ProviderTradeNo: providerRefundID, Amount: int64(rsp.Amount), Currency: currency, Payload: mustJSON(rsp)}, nil
}

func (a *lakalaAdapter) Callback(_ context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) {
	if callback == nil {
		return nil, errors.New("拉卡拉支付回调为空")
	}
	request, err := callbackRequest(callback)
	if err != nil {
		return nil, err
	}
	notify, err := lakala.ParseNotify(request)
	if err != nil {
		return nil, err
	}
	if err := lakala.VerifySign(notify, text(c, "partner_code"), text(c, "credential_code")); err != nil {
		return nil, err
	}
	return &biz.PaymentResult{Provider: biz.PaymentLakala, Status: "success", TradeNo: notify.PartnerOrderId, ProviderTradeNo: strings.TrimSpace(notify.ChannelOrderId), QueryID: strings.TrimSpace(notify.OrderId), Amount: int64(notify.TotalFee), Currency: strings.ToUpper(notify.Currency), Payload: mustJSON(notify)}, nil
}
