package payment

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"log"
	"math"
	"time"

	"hfb_sys/backend/internal/config"
	"hfb_sys/backend/internal/integrations/payment/leshua"
	"hfb_sys/backend/internal/model"
	"hfb_sys/backend/internal/modules/order"
	"hfb_sys/backend/internal/modules/wallet"

	"gorm.io/datatypes"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

type Repository struct {
	db         *gorm.DB
	cfg        config.PaymentConfig
	orderRepo  *order.Repository
	walletRepo *wallet.Repository
	leshua     *leshua.Client
	provider   string
	isMockMode bool
}

const (
	channelSourceCreate = "create"
	channelSourceQuery  = "query"
	channelSourceNotify = "notify"
	channelSourceMock   = "mock"
)

var refundBizTypes = []string{
	"cancel_refund",
	"admin_close_refund",
	"admin_refund",
	"checkout_refund",
	"deposit_refund",
	"rent_refund",
}

func NewRepository(db *gorm.DB, cfg config.PaymentConfig, orderRepo *order.Repository, walletRepo *wallet.Repository) *Repository {
	provider := cfg.Provider
	if provider == "" {
		provider = "mock"
	}
	return &Repository{
		db:         db,
		cfg:        cfg,
		orderRepo:  orderRepo,
		walletRepo: walletRepo,
		leshua:     leshua.NewClient(cfg.Leshua),
		provider:   provider,
		isMockMode: provider != "leshua",
	}
}

func (r *Repository) Start(userID uint64, orderID uint64, req StartPaymentRequest, clientIP string) (*PaymentDTO, error) {
	payment, orderRow, err := r.preparePayment(userID, orderID, req)
	if err != nil {
		return nil, err
	}
	if payment.Status == "paid" {
		dto := toDTO(*payment)
		return &dto, nil
	}
	if r.isMockMode {
		if err := r.confirmPaid(payment, "2", time.Now(), map[string]string{
			"mock":            "true",
			"third_order_id":  payment.ThirdOrderID,
			"leshua_order_id": payment.ProviderOrderID,
			"status":          "2",
		}, channelSourceMock); err != nil {
			return nil, err
		}
		latest, err := r.findPaymentByID(payment.ID)
		if err != nil {
			return nil, err
		}
		dto := toDTO(*latest)
		return &dto, nil
	}
	if payment.Status == "paying" && (payment.TDCode != "" || payment.JSPayURL != "" || payment.JSPayInfo != "") {
		dto := toDTO(*payment)
		return &dto, nil
	}

	resp, rawReq, err := r.leshua.CreatePayment(context.Background(), leshua.CreatePaymentRequest{
		ThirdOrderID: payment.ThirdOrderID,
		AmountCent:   payment.AmountCent,
		PayWay:       payment.PayWay,
		JSPayFlag:    payment.JSPayFlag,
		NotifyURL:    r.cfg.Leshua.NotifyURL,
		JumpURL:      r.cfg.Leshua.JumpURL,
		ClientIP:     clientIP,
		Body:         "租号订单 " + orderRow.OrderNo,
		Attach:       orderRow.OrderNo,
	})
	if err != nil {
		_ = r.markPaymentFailed(payment.ID, nil, err.Error())
		return nil, err
	}
	if resp.RespCode != "0" || resp.ResultCode != "0" {
		_ = r.markPaymentFailed(payment.ID, resp.Raw, resp.ErrorMessage)
		return nil, ErrPaymentUnavailable
	}
	if err := r.db.Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(map[string]any{
		"status":            "paying",
		"provider_order_id": resp.ProviderOrderID,
		"pay_way":           firstNonEmpty(resp.PayWay, payment.PayWay),
		"td_code":           resp.TDCode,
		"jspay_url":         resp.JSPayURL,
		"jspay_info":        resp.JSPayInfo,
		"raw_request":       jsonMap(rawReq),
		"raw_response":      jsonMap(withRawSource(resp.Raw, channelSourceCreate)),
	}).Error; err != nil {
		return nil, err
	}
	latest, err := r.findPaymentByID(payment.ID)
	if err != nil {
		return nil, err
	}
	dto := toDTO(*latest)
	return &dto, nil
}

func (r *Repository) StartWalletRecharge(userID uint64, req WalletRechargePaymentRequest, clientIP string) (*PaymentDTO, error) {
	amountCent := moneyCent(req.Amount)
	if userID == 0 || req.Amount < MinWalletRechargeAmount || amountCent <= 0 {
		return nil, ErrPaymentCannotStart
	}
	payment, err := r.createWalletRechargePayment(userID, amountCent, req)
	if err != nil {
		return nil, err
	}
	if r.isMockMode {
		if err := r.confirmPaid(payment, "2", time.Now(), map[string]string{
			"mock":            "true",
			"third_order_id":  payment.ThirdOrderID,
			"leshua_order_id": payment.ProviderOrderID,
			"status":          "2",
		}, channelSourceMock); err != nil {
			return nil, err
		}
		latest, err := r.findPaymentByID(payment.ID)
		if err != nil {
			return nil, err
		}
		dto := toDTO(*latest)
		return &dto, nil
	}
	resp, rawReq, err := r.leshua.CreatePayment(context.Background(), leshua.CreatePaymentRequest{
		ThirdOrderID: payment.ThirdOrderID,
		AmountCent:   payment.AmountCent,
		PayWay:       payment.PayWay,
		JSPayFlag:    payment.JSPayFlag,
		NotifyURL:    r.cfg.Leshua.NotifyURL,
		JumpURL:      r.cfg.Leshua.JumpURL,
		ClientIP:     clientIP,
		Body:         "钱包充值 " + payment.PaymentNo,
		Attach:       payment.PaymentNo,
	})
	if err != nil {
		_ = r.markPaymentFailed(payment.ID, nil, err.Error())
		return nil, err
	}
	if resp.RespCode != "0" || resp.ResultCode != "0" {
		_ = r.markPaymentFailed(payment.ID, resp.Raw, resp.ErrorMessage)
		return nil, ErrPaymentUnavailable
	}
	if err := r.db.Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(map[string]any{
		"status":            "paying",
		"provider_order_id": resp.ProviderOrderID,
		"pay_way":           firstNonEmpty(resp.PayWay, payment.PayWay),
		"td_code":           resp.TDCode,
		"jspay_url":         resp.JSPayURL,
		"jspay_info":        resp.JSPayInfo,
		"raw_request":       jsonMap(rawReq),
		"raw_response":      jsonMap(withRawSource(resp.Raw, channelSourceCreate)),
	}).Error; err != nil {
		return nil, err
	}
	latest, err := r.findPaymentByID(payment.ID)
	if err != nil {
		return nil, err
	}
	dto := toDTO(*latest)
	return &dto, nil
}

func (r *Repository) QueryWalletRecharge(userID uint64, paymentID uint64) (*PaymentDTO, error) {
	var payment model.PaymentOrder
	if err := r.db.Where("id = ? AND user_id = ? AND order_id = 0", paymentID, userID).First(&payment).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, ErrPaymentNotFound
		}
		return nil, err
	}
	if payment.Status == "paid" || r.isMockMode {
		dto := toDTO(payment)
		return &dto, nil
	}
	resp, err := r.leshua.QueryPayment(context.Background(), payment.ThirdOrderID, payment.ProviderOrderID)
	if err != nil {
		return nil, err
	}
	if err := r.applyChannelStatus(&payment, resp.Status, resp.PayTime, resp.Raw, channelSourceQuery); err != nil {
		return nil, err
	}
	latest, err := r.findPaymentByID(payment.ID)
	if err != nil {
		return nil, err
	}
	dto := toDTO(*latest)
	return &dto, nil
}

func (r *Repository) Query(userID uint64, orderID uint64) (*PaymentDTO, error) {
	var payment model.PaymentOrder
	if err := r.db.Where("order_id = ? AND user_id = ? AND biz_type = ?", orderID, userID, "order_pay").Order("id DESC").First(&payment).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, ErrPaymentNotFound
		}
		return nil, err
	}
	if payment.Status == "paid" || r.isMockMode {
		dto := toDTO(payment)
		return &dto, nil
	}
	resp, err := r.leshua.QueryPayment(context.Background(), payment.ThirdOrderID, payment.ProviderOrderID)
	if err != nil {
		return nil, err
	}
	if err := r.applyChannelStatus(&payment, resp.Status, resp.PayTime, resp.Raw, channelSourceQuery); err != nil {
		return nil, err
	}
	latest, err := r.findPaymentByID(payment.ID)
	if err != nil {
		return nil, err
	}
	dto := toDTO(*latest)
	return &dto, nil
}

func (r *Repository) HandleLeshuaNotify(params map[string]string, rawPayload string, contentType string) (*NotifyResult, error) {
	var verify leshua.VerifyNotifyResult
	if !r.isMockMode {
		verify = r.leshua.VerifyNotifyDetail(params)
		if !verify.OK {
			log.Printf(
				"[payment] leshua notify verify failed third_order_id=%s got=%s expected=%s keys=%v base_string=%s",
				params["third_order_id"],
				verify.Got,
				verify.Expected["notify_key"],
				verify.ParamKeys,
				verify.BaseString["notify_key"],
			)
			if err := r.recordNotifyDiagnostic(params, rawPayload, contentType, verify, "verify_failed"); err != nil {
				log.Printf("[payment] leshua notify diagnostic save failed third_order_id=%s err=%v", params["third_order_id"], err)
			}
			return nil, ErrPaymentVerifyFailed
		}
		log.Printf("[payment] leshua notify verified third_order_id=%s matched_key=%s", params["third_order_id"], verify.MatchedKey)
	}
	// 退款通知会携带 merchant_refund_id 或 leshua_refund_id。
	if params["merchant_refund_id"] != "" || params["leshua_refund_id"] != "" {
		return r.HandleRefundNotify(params, rawPayload, contentType)
	}
	thirdOrderID := params["third_order_id"]
	if thirdOrderID == "" {
		return nil, ErrPaymentNotFound
	}
	var payment model.PaymentOrder
	if err := r.db.Where("third_order_id = ?", thirdOrderID).First(&payment).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, ErrPaymentNotFound
		}
		return nil, err
	}
	if amount := parseCent(params["amount"]); amount > 0 && amount != payment.AmountCent {
		if err := r.recordNotifyDiagnostic(params, rawPayload, contentType, verify, "amount_mismatch"); err != nil {
			log.Printf("[payment] leshua notify diagnostic save failed third_order_id=%s err=%v", params["third_order_id"], err)
		}
		return nil, ErrPaymentVerifyFailed
	}
	raw := withNotifyDiagnostic(params, rawPayload, contentType, verify, "verified")
	if err := r.applyChannelStatus(&payment, params["status"], params["pay_time"], raw, channelSourceNotify); err != nil {
		return nil, err
	}
	return &NotifyResult{OK: true, Message: "000000"}, nil
}

// StartRefund 创建退款单，并在本地落库后调用乐刷退款接口。
func (r *Repository) StartRefund(orderID uint64, refundAmountCent int64, bizType string, remark string) (*RefundDTO, error) {
	var originalPayment model.PaymentOrder
	if err := r.db.Where("order_id = ? AND status = 'paid' AND biz_type = 'order_pay'", orderID).Order("id DESC").First(&originalPayment).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, ErrPaymentNotFound
		}
		return nil, err
	}

	var existingRefund model.PaymentOrder
	err := r.db.Where("order_id = ? AND biz_type = ? AND status NOT IN ('failed')", orderID, bizType).Order("id DESC").First(&existingRefund).Error
	if err == nil {
		dto := toRefundDTO(existingRefund)
		return &dto, nil
	}
	if err != gorm.ErrRecordNotFound {
		return nil, err
	}

	paymentNo, err := newPaymentNo()
	if err != nil {
		return nil, err
	}
	merchantRefundID := "REF" + paymentNo[3:]

	refundOrder := model.PaymentOrder{
		PaymentNo:       paymentNo,
		OrderID:         orderID,
		OrderNo:         originalPayment.OrderNo,
		UserID:          originalPayment.UserID,
		Provider:        r.provider,
		MerchantID:      r.cfg.Leshua.MerchantID,
		ThirdOrderID:    merchantRefundID,
		ProviderOrderID: "",
		PayWay:          originalPayment.PayWay,
		JSPayFlag:       originalPayment.JSPayFlag,
		AmountCent:      refundAmountCent,
		BizType:         bizType,
		Status:          "refunding",
	}

	if r.isMockMode {
		refundOrder.ProviderOrderID = "MOCKREF" + merchantRefundID
		refundOrder.Status = "refunded"
		now := time.Now()
		refundOrder.PaidAt = &now
		if remark != "" {
			refundOrder.RawResponse = datatypes.JSON([]byte(fmt.Sprintf(`{"mock":"true","remark":"%s"}`, remark)))
		}
		if err := r.db.Create(&refundOrder).Error; err != nil {
			return nil, err
		}
		if err := r.updateOrderRefundStatus(orderID, refundAmountCent); err != nil {
			log.Printf("[payment] mock update order refund status failed order_id=%d err=%v", orderID, err)
		}
		dto := toRefundDTO(refundOrder)
		return &dto, nil
	}

	if err := r.db.Create(&refundOrder).Error; err != nil {
		return nil, err
	}
	if err := r.markOrderRefunding(orderID, refundAmountCent); err != nil {
		log.Printf("[payment] mark order refunding failed order_id=%d err=%v", orderID, err)
	}

	resp, rawReq, err := r.leshua.CreateRefund(context.Background(), leshua.CreateRefundRequest{
		ThirdOrderID:     originalPayment.ThirdOrderID,
		LeshuaOrderID:    originalPayment.ProviderOrderID,
		MerchantRefundID: merchantRefundID,
		RefundAmountCent: refundAmountCent,
		NotifyURL:        r.cfg.Leshua.NotifyURL,
		Attach:           originalPayment.OrderNo,
	})
	if err != nil {
		_ = r.markRefundFailed(refundOrder.ID, orderID, refundAmountCent, map[string]string{"error": err.Error()})
		return nil, err
	}
	if resp.RespCode != "0" || resp.ResultCode != "0" {
		_ = r.markRefundFailed(refundOrder.ID, orderID, refundAmountCent, resp.Raw)
		return nil, ErrPaymentUnavailable
	}

	refundStatus := "refunding"
	var paidAt *time.Time
	if resp.Status == "11" {
		refundStatus = "refunded"
		now := time.Now()
		paidAt = &now
	} else if resp.Status == "12" {
		refundStatus = "failed"
	}
	if err := r.db.Model(&model.PaymentOrder{}).Where("id = ?", refundOrder.ID).Updates(map[string]any{
		"status":            refundStatus,
		"provider_order_id": resp.LeshuaRefundID,
		"raw_request":       jsonMap(rawReq),
		"raw_response":      jsonMap(withRawSource(resp.Raw, channelSourceCreate)),
		"paid_at":           paidAt,
	}).Error; err != nil {
		return nil, err
	}

	if refundStatus == "refunded" {
		_ = r.updateOrderRefundStatus(orderID, refundAmountCent)
		refundOrder.PaidAt = paidAt
	} else if refundStatus == "failed" {
		_ = r.markOrderRefundFailed(orderID, refundAmountCent)
	} else {
		_ = r.markOrderRefunding(orderID, refundAmountCent)
	}
	refundOrder.Status = refundStatus
	refundOrder.ProviderOrderID = resp.LeshuaRefundID

	dto := toRefundDTO(refundOrder)
	return &dto, nil
}

// QueryRefundStatus 查询订单最近一笔退款状态。
func (r *Repository) QueryRefundStatus(orderID uint64) (*RefundDTO, error) {
	var payment model.PaymentOrder
	if err := r.db.Where("order_id = ? AND biz_type IN ?", orderID, refundBizTypes).Order("id DESC").First(&payment).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, ErrPaymentNotFound
		}
		return nil, err
	}
	if payment.Status == "refunded" || payment.Status == "failed" || r.isMockMode {
		dto := toRefundDTO(payment)
		return &dto, nil
	}
	resp, err := r.leshua.QueryRefund(context.Background(), leshua.QueryRefundRequest{
		ThirdOrderID:     payment.ThirdOrderID,
		MerchantRefundID: payment.ThirdOrderID,
		LeshuaRefundID:   payment.ProviderOrderID,
	})
	if err != nil {
		return nil, err
	}
	if resp.Status == "11" {
		now := time.Now()
		if err := r.db.Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(map[string]any{
			"status":       "refunded",
			"paid_at":      now,
			"raw_response": jsonMap(withRawSource(resp.Raw, channelSourceQuery)),
		}).Error; err != nil {
			return nil, err
		}
		payment.Status = "refunded"
		payment.PaidAt = &now
		_ = r.updateOrderRefundStatus(orderID, payment.AmountCent)
	} else if resp.Status == "12" {
		if err := r.db.Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(map[string]any{
			"status":       "failed",
			"raw_response": jsonMap(withRawSource(resp.Raw, channelSourceQuery)),
		}).Error; err != nil {
			return nil, err
		}
		payment.Status = "failed"
		_ = r.markOrderRefundFailed(orderID, payment.AmountCent)
	}
	dto := toRefundDTO(payment)
	return &dto, nil
}

// HandleRefundNotify 处理乐刷退款通知。
func (r *Repository) HandleRefundNotify(params map[string]string, rawPayload string, contentType string) (*NotifyResult, error) {
	var verify leshua.VerifyNotifyResult
	if !r.isMockMode {
		verify = r.leshua.VerifyNotifyDetail(params)
		if !verify.OK {
			log.Printf("[payment] refund notify verify failed merchant_refund_id=%s", params["merchant_refund_id"])
			return nil, ErrPaymentVerifyFailed
		}
	}
	merchantRefundID := params["merchant_refund_id"]
	if merchantRefundID == "" {
		return nil, ErrPaymentNotFound
	}
	var payment model.PaymentOrder
	if err := r.db.Where("third_order_id = ?", merchantRefundID).First(&payment).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, ErrPaymentNotFound
		}
		return nil, err
	}
	raw := withNotifyDiagnostic(params, rawPayload, contentType, verify, "verified")
	status := params["status"]
	switch status {
	case "11":
		now := time.Now()
		if err := r.db.Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(map[string]any{
			"status":       "refunded",
			"paid_at":      now,
			"notified_at":  now,
			"raw_response": jsonMap(raw),
		}).Error; err != nil {
			return nil, err
		}
		_ = r.updateOrderRefundStatus(payment.OrderID, payment.AmountCent)
	case "12":
		r.db.Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(map[string]any{
			"status":       "failed",
			"notified_at":  time.Now(),
			"raw_response": jsonMap(raw),
		})
		_ = r.markOrderRefundFailed(payment.OrderID, payment.AmountCent)
	default:
		r.db.Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(map[string]any{
			"status":       "refunding",
			"raw_response": jsonMap(raw),
		})
	}
	return &NotifyResult{OK: true, Message: "000000"}, nil
}

// updateOrderRefundStatus 更新订单退款成功状态。
func (r *Repository) updateOrderRefundStatus(orderID uint64, refundAmountCent int64) error {
	now := time.Now()
	return r.db.Model(&model.RentalOrder{}).Where("id = ?", orderID).Updates(map[string]any{
		"refund_status":      "refunded",
		"refund_amount_cent": refundAmountCent,
		"refunded_at":        now,
	}).Error
}

func (r *Repository) markOrderRefunding(orderID uint64, refundAmountCent int64) error {
	return r.db.Model(&model.RentalOrder{}).Where("id = ?", orderID).Updates(map[string]any{
		"refund_status":      "refunding",
		"refund_amount_cent": refundAmountCent,
		"refunded_at":        nil,
	}).Error
}

func (r *Repository) markOrderRefundFailed(orderID uint64, refundAmountCent int64) error {
	return r.db.Model(&model.RentalOrder{}).Where("id = ?", orderID).Updates(map[string]any{
		"refund_status":      "failed",
		"refund_amount_cent": refundAmountCent,
		"refunded_at":        nil,
	}).Error
}

func (r *Repository) markRefundFailed(paymentID uint64, orderID uint64, refundAmountCent int64, raw map[string]string) error {
	if raw == nil {
		raw = map[string]string{"error": "refund failed"}
	}
	if err := r.db.Model(&model.PaymentOrder{}).Where("id = ?", paymentID).Updates(map[string]any{
		"status":       "failed",
		"raw_response": jsonMap(raw),
	}).Error; err != nil {
		return err
	}
	return r.markOrderRefundFailed(orderID, refundAmountCent)
}

// toRefundDTO 将支付表里的退款单转换为接口 DTO。
func toRefundDTO(payment model.PaymentOrder) RefundDTO {
	return RefundDTO{
		ID:              payment.ID,
		PaymentNo:       payment.PaymentNo,
		OrderID:         payment.OrderID,
		OrderNo:         payment.OrderNo,
		BizType:         payment.BizType,
		AmountCent:      payment.AmountCent,
		Status:          payment.Status,
		ProviderOrderID: payment.ProviderOrderID,
		PaidAt:          payment.PaidAt,
		CreatedAt:       payment.CreatedAt,
		UpdatedAt:       payment.UpdatedAt,
	}
}

func (r *Repository) preparePayment(userID uint64, orderID uint64, req StartPaymentRequest) (*model.PaymentOrder, *model.RentalOrder, error) {
	var paymentID uint64
	var orderRow model.RentalOrder
	err := r.db.Transaction(func(tx *gorm.DB) error {
		var row model.RentalOrder
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("id = ? AND renter_id = ?", orderID, userID).
			First(&row).Error; err != nil {
			return err
		}
		if row.Status != "pending_payment" {
			return ErrPaymentCannotStart
		}
		amountCent := moneyCent(row.RentAmount + row.DepositAmount)
		if amountCent <= 0 {
			return ErrPaymentCannotStart
		}
		var existing model.PaymentOrder
		err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("order_id = ? AND biz_type = ?", row.ID, "order_pay").
			Order("id DESC").
			First(&existing).Error
		if err == nil {
			existing.PayWay = firstNonEmpty(req.PayWay, existing.PayWay, r.cfg.Leshua.PayWay, "ZFBZF")
			existing.JSPayFlag = firstNonEmpty(req.JSPayFlag, existing.JSPayFlag, r.cfg.Leshua.JSPayFlag, "2")
			existing.AmountCent = amountCent
			existing.Provider = r.provider
			existing.MerchantID = r.cfg.Leshua.MerchantID
			if r.isMockMode && existing.ProviderOrderID == "" {
				existing.ProviderOrderID = "MOCK" + existing.ThirdOrderID
			}
			if err := tx.Save(&existing).Error; err != nil {
				return err
			}
			paymentID = existing.ID
			orderRow = row
			return nil
		}
		if err != gorm.ErrRecordNotFound {
			return err
		}
		paymentNo, err := newPaymentNo()
		if err != nil {
			return err
		}
		payment := model.PaymentOrder{
			PaymentNo:       paymentNo,
			OrderID:         row.ID,
			OrderNo:         row.OrderNo,
			UserID:          row.RenterID,
			Provider:        r.provider,
			MerchantID:      r.cfg.Leshua.MerchantID,
			ThirdOrderID:    row.OrderNo,
			ProviderOrderID: "",
			PayWay:          firstNonEmpty(req.PayWay, r.cfg.Leshua.PayWay, "ZFBZF"),
			JSPayFlag:       firstNonEmpty(req.JSPayFlag, r.cfg.Leshua.JSPayFlag, "2"),
			AmountCent:      amountCent,
			BizType:         "order_pay",
			Status:          "created",
		}
		if r.isMockMode {
			payment.ProviderOrderID = "MOCK" + row.OrderNo
			payment.TDCode = "mock://leshua/pay/" + row.OrderNo
		}
		if err := tx.Create(&payment).Error; err != nil {
			return err
		}
		paymentID = payment.ID
		orderRow = row
		return nil
	})
	if err != nil {
		return nil, nil, err
	}
	payment, err := r.findPaymentByID(paymentID)
	if err != nil {
		return nil, nil, err
	}
	return payment, &orderRow, nil
}

func (r *Repository) createWalletRechargePayment(userID uint64, amountCent int64, req WalletRechargePaymentRequest) (*model.PaymentOrder, error) {
	paymentNo, err := newPaymentNo()
	if err != nil {
		return nil, err
	}
	payment := model.PaymentOrder{
		PaymentNo:       paymentNo,
		OrderID:         0,
		OrderNo:         paymentNo,
		UserID:          userID,
		Provider:        r.provider,
		MerchantID:      r.cfg.Leshua.MerchantID,
		ThirdOrderID:    paymentNo,
		ProviderOrderID: "",
		PayWay:          firstNonEmpty(req.PayWay, r.cfg.Leshua.PayWay, "ZFBZF"),
		JSPayFlag:       firstNonEmpty(req.JSPayFlag, r.cfg.Leshua.JSPayFlag, "2"),
		AmountCent:      amountCent,
		BizType:         "wallet_recharge",
		Status:          "created",
	}
	if r.isMockMode {
		payment.ProviderOrderID = "MOCK" + paymentNo
		payment.TDCode = "mock://leshua/recharge/" + paymentNo
	}
	if err := r.db.Create(&payment).Error; err != nil {
		return nil, err
	}
	return &payment, nil
}

func (r *Repository) applyChannelStatus(payment *model.PaymentOrder, status string, payTime string, raw map[string]string, source string) error {
	switch status {
	case "2", "30":
		paidAt := parseLeshuaTime(payTime)
		if paidAt == nil {
			now := time.Now()
			paidAt = &now
		}
		return r.confirmPaid(payment, status, *paidAt, raw, source)
	case "6":
		return r.updateChannelStatus(payment.ID, "closed", raw, source)
	case "8":
		return r.updateChannelStatus(payment.ID, "failed", raw, source)
	default:
		return r.updateChannelStatus(payment.ID, "paying", raw, source)
	}
}

func (r *Repository) updateChannelStatus(paymentID uint64, status string, raw map[string]string, source string) error {
	updates := map[string]any{
		"status":       status,
		"raw_response": jsonMap(withRawSource(raw, source)),
	}
	if source == channelSourceNotify {
		updates["notified_at"] = time.Now()
	}
	return r.db.Model(&model.PaymentOrder{}).Where("id = ?", paymentID).Updates(updates).Error
}

func (r *Repository) confirmPaid(payment *model.PaymentOrder, status string, paidAt time.Time, raw map[string]string, source string) error {
	if payment.Status != "paid" {
		if payment.OrderID == 0 {
			if r.walletRepo == nil {
				return ErrDependencyUnavailable
			}
			if err := r.walletRepo.ConfirmRechargeFromChannel(payment.UserID, firstNonEmpty(payment.ProviderOrderID, payment.PaymentNo), payment.AmountCent); err != nil {
				return err
			}
		} else {
			if r.orderRepo == nil {
				return ErrDependencyUnavailable
			}
			if err := r.orderRepo.ConfirmPaidFromChannel(payment.OrderID, firstNonEmpty(payment.ProviderOrderID, payment.PaymentNo)); err != nil {
				return err
			}
		}
	}
	updates := map[string]any{
		"status":            "paid",
		"provider_order_id": firstNonEmpty(raw["leshua_order_id"], payment.ProviderOrderID),
		"raw_response":      jsonMap(withRawSource(raw, source)),
		"paid_at":           paidAt,
	}
	if source == channelSourceNotify {
		updates["notified_at"] = time.Now()
	}
	return r.db.Model(&model.PaymentOrder{}).Where("id = ?", payment.ID).Updates(updates).Error
}

func (r *Repository) markPaymentFailed(paymentID uint64, raw map[string]string, message string) error {
	if raw == nil {
		raw = map[string]string{"error": message}
	}
	return r.db.Model(&model.PaymentOrder{}).Where("id = ?", paymentID).Updates(map[string]any{
		"status":       "failed",
		"raw_response": jsonMap(raw),
	}).Error
}

func (r *Repository) findPaymentByID(paymentID uint64) (*model.PaymentOrder, error) {
	var payment model.PaymentOrder
	if err := r.db.First(&payment, paymentID).Error; err != nil {
		return nil, err
	}
	return &payment, nil
}

func toDTO(payment model.PaymentOrder) PaymentDTO {
	return PaymentDTO{
		ID:              payment.ID,
		PaymentNo:       payment.PaymentNo,
		OrderID:         payment.OrderID,
		OrderNo:         payment.OrderNo,
		Provider:        payment.Provider,
		ThirdOrderID:    payment.ThirdOrderID,
		ProviderOrderID: payment.ProviderOrderID,
		PayWay:          payment.PayWay,
		JSPayFlag:       payment.JSPayFlag,
		AmountCent:      payment.AmountCent,
		Status:          payment.Status,
		TDCode:          payment.TDCode,
		JSPayURL:        payment.JSPayURL,
		JSPayInfo:       payment.JSPayInfo,
		Paid:            payment.Status == "paid",
		PaidAt:          payment.PaidAt,
		CreatedAt:       payment.CreatedAt,
		UpdatedAt:       payment.UpdatedAt,
	}
}

func moneyCent(value float64) int64 {
	return int64(math.Round(value * 100))
}

func parseCent(value string) int64 {
	var amount int64
	_, _ = fmt.Sscanf(value, "%d", &amount)
	return amount
}

func parseLeshuaTime(value string) *time.Time {
	if value == "" {
		return nil
	}
	for _, layout := range []string{"2006-01-02 15:04:05", time.RFC3339} {
		parsed, err := time.ParseInLocation(layout, value, time.Local)
		if err == nil {
			return &parsed
		}
	}
	return nil
}

func jsonMap(value map[string]string) datatypes.JSON {
	if value == nil {
		return nil
	}
	raw, err := json.Marshal(value)
	if err != nil {
		return nil
	}
	return datatypes.JSON(raw)
}

func withRawSource(raw map[string]string, source string) map[string]string {
	out := map[string]string{}
	for key, value := range raw {
		out[key] = value
	}
	if source != "" {
		out["_source"] = source
	}
	out["_recorded_at"] = time.Now().Format(time.RFC3339)
	return out
}

func (r *Repository) recordNotifyDiagnostic(params map[string]string, rawPayload string, contentType string, verify leshua.VerifyNotifyResult, status string) error {
	thirdOrderID := params["third_order_id"]
	if thirdOrderID == "" {
		return nil
	}
	raw := withNotifyDiagnostic(params, rawPayload, contentType, verify, status)
	return r.db.Model(&model.PaymentOrder{}).
		Where("third_order_id = ?", thirdOrderID).
		Update("raw_response", jsonMap(raw)).Error
}

func withNotifyDiagnostic(params map[string]string, rawPayload string, contentType string, verify leshua.VerifyNotifyResult, status string) map[string]string {
	raw := withRawSource(params, channelSourceNotify)
	raw["_notify_diagnostic_status"] = status
	raw["_raw_payload"] = rawPayload
	raw["_raw_content_type"] = contentType
	raw["_sign_got"] = verify.Got
	raw["_sign_matched_key"] = verify.MatchedKey
	raw["_sign_expected"] = jsonString(verify.Expected)
	raw["_sign_base_strings"] = jsonString(verify.BaseString)
	return raw
}

func jsonString(value map[string]string) string {
	if len(value) == 0 {
		return "{}"
	}
	raw, err := json.Marshal(value)
	if err != nil {
		return "{}"
	}
	return string(raw)
}

func newPaymentNo() (string, error) {
	buf := make([]byte, 4)
	if _, err := rand.Read(buf); err != nil {
		return "", err
	}
	return fmt.Sprintf("PAY%d%s", time.Now().UnixNano(), hex.EncodeToString(buf)), nil
}

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