package order

import (
	"crypto/rand"
	"encoding/json"
	"errors"
	"log"
	"math"
	"strconv"
	"time"

	"hfb_sys/backend/internal/auditlog"
	"hfb_sys/backend/internal/model"
	"hfb_sys/backend/internal/modules/chat"
	"hfb_sys/backend/internal/modules/notification"
	"hfb_sys/backend/internal/modules/wallet"
	"hfb_sys/backend/pkg/money"

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

// RefundFunc 由 payment 模块注入，避免 order 与 payment 形成循环依赖。
type RefundFunc func(orderID uint64, refundAmountCent int64, bizType string, remark string) (status string, err error)

type refundAction struct {
	OrderID          uint64
	RefundAmountCent int64
	BizType          string
	Remark           string
}

type Repository struct {
	db         *gorm.DB
	chatRepo   *chat.Repository
	refundFunc RefundFunc
}

const defaultPendingPaymentTimeoutMinutes = 15

func NewRepository(db *gorm.DB) *Repository {
	return &Repository{db: db}
}

func (r *Repository) SetChatRepo(cr *chat.Repository) {
	r.chatRepo = cr
}

func (r *Repository) SetRefundFunc(fn RefundFunc) {
	r.refundFunc = fn
}

type orderPricing struct {
	RentAmount      float64
	OwnerRentAmount float64
	PlatformFee     float64
}

type checkoutSettlement struct {
	OwnerRentIncome     float64
	DepositCompensation float64
	OwnerIncome         float64
	RentRefund          float64
	DepositRefund       float64
	RenterRefund        float64
	PlatformFee         float64
	ActualRentAmount    float64
}

func orderDurationHours(order model.RentalOrder) int {
	if order.EstimatedDurationHours > 0 {
		return order.EstimatedDurationHours
	}
	return internalOrderHours
}

func buildOrderPricing(listing model.RentalListing, account model.GameAccount) orderPricing {
	rentAmount := roundMoney(listing.Price)
	ownerRentAmount := readSnapshotPrice(account.AssetSummary, "seller_total_price")
	if ownerRentAmount <= 0 || ownerRentAmount > rentAmount {
		ownerRentAmount = rentAmount
	}
	platformFee := readSnapshotPrice(account.AssetSummary, "platform_markup_amount")
	if platformFee <= 0 || roundMoney(ownerRentAmount+platformFee) != rentAmount {
		platformFee = roundMoney(rentAmount - ownerRentAmount)
	}
	if platformFee < 0 {
		platformFee = 0
	}
	return orderPricing{
		RentAmount:      rentAmount,
		OwnerRentAmount: roundMoney(ownerRentAmount),
		PlatformFee:     roundMoney(platformFee),
	}
}

func readSnapshotPrice(raw datatypes.JSON, key string) float64 {
	return readOrderSnapshotPrice(raw, key)
}

func readOrderSnapshotPrice(raw datatypes.JSON, key string) float64 {
	if len(raw) == 0 {
		return 0
	}
	var snapshot map[string]any
	if err := json.Unmarshal(raw, &snapshot); err != nil {
		return 0
	}
	if assetSummary, ok := snapshot["asset_summary"].(map[string]any); ok {
		snapshot = assetSummary
	}
	breakdown, ok := snapshot["price_breakdown"].(map[string]any)
	if !ok {
		return 0
	}
	return readJSONNumber(breakdown[key])
}

func readJSONNumber(value any) float64 {
	switch typed := value.(type) {
	case float64:
		return typed
	case float32:
		return float64(typed)
	case int:
		return float64(typed)
	case int64:
		return float64(typed)
	case json.Number:
		number, err := typed.Float64()
		if err != nil {
			return 0
		}
		return number
	case string:
		number, err := strconv.ParseFloat(typed, 64)
		if err != nil {
			return 0
		}
		return number
	default:
		return 0
	}
}

func (r *Repository) Create(renterID uint64, req CreateRequest) (*OrderDTO, error) {
	var createdID uint64
	err := r.db.Transaction(func(tx *gorm.DB) error {
		var listing model.RentalListing
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&listing, req.ListingID).Error; err != nil {
			return err
		}
		if listing.Status != "published" || listing.ReviewStatus != "approved" || listing.InTransaction {
			return ErrListingUnavailable
		}
		if listing.OwnerID == renterID {
			return ErrCannotRentOwnListing
		}

		var account model.GameAccount
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&account, listing.AccountID).Error; err != nil {
			return err
		}
		snapshot, err := makeAccountSnapshot(account)
		if err != nil {
			return err
		}
		orderNo, err := newOrderNo()
		if err != nil {
			return err
		}
		rentHours := internalOrderHours
		pricing := buildOrderPricing(listing, account)
		order := model.RentalOrder{
			OrderNo:                orderNo,
			ListingID:              listing.ID,
			AccountID:              listing.AccountID,
			OwnerID:                listing.OwnerID,
			RenterID:               renterID,
			EstimatedDurationHours: rentHours,
			RentAmount:             pricing.RentAmount,
			OwnerRentAmount:        pricing.OwnerRentAmount,
			DepositAmount:          listing.DepositAmount,
			PlatformFee:            pricing.PlatformFee,
			AccountSnapshot:        snapshot,
			Status:                 "pending_payment",
			HandoffStatus:          "none",
			SettlementStatus:       "unsettled",
		}
		if err := tx.Create(&order).Error; err != nil {
			return err
		}
		orderID := order.ID
		if err := notification.Append(tx,
			notification.Entry{
				UserID:  order.RenterID,
				Type:    "order",
				Title:   "订单已创建",
				Content: "订单已创建，请在有效时间内完成支付。",
				BizType: "order",
				BizID:   &orderID,
			},
		); err != nil {
			return err
		}
		listing.InTransaction = true
		if err := tx.Save(&listing).Error; err != nil {
			return err
		}
		createdID = order.ID
		return nil
	})
	if err != nil {
		return nil, err
	}
	return r.FindForUser(renterID, createdID)
}

// Pay 保留旧接口兼容，但真实付款必须走 payment 模块的渠道支付入口。
func (r *Repository) Pay(userID uint64, orderID uint64) error {
	return ErrChannelPaymentRequired
}

// ConfirmPaidFromChannel 在乐刷确认支付后推进订单状态；租客资金不进入站内钱包。
func (r *Repository) ConfirmPaidFromChannel(orderID uint64, providerBizNo string) error {
	var newConvID uint64
	err := r.db.Transaction(func(tx *gorm.DB) error {
		var order model.RentalOrder
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil {
			return err
		}
		if order.Status == "pending_handoff" || order.Status == "renting" {
			return nil
		}
		if order.Status != "pending_payment" {
			return ErrOrderCannotPay
		}

		var listing model.RentalListing
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&listing, order.ListingID).Error; err != nil {
			return err
		}
		if listing.Status != "published" || listing.ReviewStatus != "approved" || !listing.InTransaction {
			return ErrListingUnavailable
		}
		var account model.GameAccount
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&account, order.AccountID).Error; err != nil {
			return err
		}

		// 租客已通过外部渠道付款，这里不写租客钱包流水。
		orderID := order.ID
		order.Status = "pending_handoff"
		order.HandoffStatus = "pending_owner"
		listing.Status = "rented"
		account.Status = "rented"
		conv, err := chat.EnsureOrderConversation(tx, order)
		if err != nil {
			return err
		}
		newConvID = conv.ID
		if err := notification.Append(tx,
			notification.Entry{
				UserID:  order.OwnerID,
				Type:    "order",
				Title:   "收到新的租号订单",
				Content: "租客已完成支付，请尽快提交交接说明。",
				BizType: "order",
				BizID:   &orderID,
			},
			notification.Entry{
				UserID:  order.RenterID,
				Type:    "order",
				Title:   "订单支付成功",
				Content: "支付已完成，等待号主提交交接说明。",
				BizType: "order",
				BizID:   &orderID,
			},
		); err != nil {
			return err
		}
		if err := tx.Save(&order).Error; err != nil {
			return err
		}
		if err := tx.Save(&listing).Error; err != nil {
			return err
		}
		return tx.Save(&account).Error
	})
	if err != nil {
		return err
	}
	if newConvID > 0 && r.chatRepo != nil {
		r.chatRepo.NotifyNewConversation(newConvID)
	}
	return nil
}

func (r *Repository) Cancel(userID uint64, orderID uint64) error {
	var refund *refundAction
	err := r.db.Transaction(func(tx *gorm.DB) error {
		var order model.RentalOrder
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("id = ? AND renter_id = ?", orderID, userID).
			First(&order).Error; err != nil {
			return err
		}
		if order.Status != "pending_payment" && order.Status != "pending_handoff" {
			return ErrOrderCannotCancel
		}
		var listing model.RentalListing
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&listing, order.ListingID).Error; err != nil {
			return err
		}
		var account model.GameAccount
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&account, order.AccountID).Error; err != nil {
			return err
		}

		beforeStatus := order.Status
		order.Status = "cancelled"
		order.HandoffStatus = "cancelled"
		orderID := order.ID
		if beforeStatus == "pending_handoff" {
			totalCent := int64(math.Round((order.RentAmount + order.DepositAmount) * 100))
			action, err := r.prepareRefund(&order, totalCent, "cancel_refund", "取消订单原路退款")
			if err != nil {
				return err
			}
			refund = action
		}
		if err := notification.Append(tx,
			notification.Entry{
				UserID:  order.OwnerID,
				Type:    "order",
				Title:   "订单已取消",
				Content: "租客已取消订单，账号已重新释放。",
				BizType: "order",
				BizID:   &orderID,
			},
			notification.Entry{
				UserID:  order.RenterID,
				Type:    "order",
				Title:   "订单取消成功",
				Content: "订单已取消，退款将原路退回您的支付账户。",
				BizType: "order",
				BizID:   &orderID,
			},
		); err != nil {
			return err
		}
		listing.Status = "published"
		listing.InTransaction = false
		account.Status = "published"
		if err := tx.Save(&order).Error; err != nil {
			return err
		}
		if err := tx.Save(&listing).Error; err != nil {
			return err
		}
		return tx.Save(&account).Error
	})
	if err != nil {
		return err
	}
	r.startRefundBestEffort(refund)
	return nil
}

func (r *Repository) SubmitHandoff(userID uint64, orderID uint64, req SubmitHandoffRequest) (*HandoffRecordDTO, error) {
	var recordID uint64
	err := r.db.Transaction(func(tx *gorm.DB) error {
		var order model.RentalOrder
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil {
			return err
		}
		if order.OwnerID != userID {
			return ErrPermissionDenied
		}
		if order.Status != "pending_handoff" || order.HandoffStatus != "pending_owner" {
			return ErrOrderCannotHandoff
		}
		record := model.HandoffRecord{
			OrderID:    order.ID,
			FromUserID: order.OwnerID,
			ToUserID:   order.RenterID,
			Type:       "owner_handoff",
			Content:    req.Content,
		}
		if err := tx.Create(&record).Error; err != nil {
			return err
		}
		order.HandoffStatus = "pending_renter_confirm"
		orderID := order.ID
		if err := notification.Append(tx, notification.Entry{
			UserID:  order.RenterID,
			Type:    "handoff",
			Title:   "号主已提交交接说明",
			Content: "请查看交接记录，确认账号可正常登录后点击确认收号。",
			BizType: "order",
			BizID:   &orderID,
		}); err != nil {
			return err
		}
		if err := tx.Save(&order).Error; err != nil {
			return err
		}
		recordID = record.ID
		return nil
	})
	if err != nil {
		return nil, err
	}
	return r.findHandoffRecord(recordID)
}

func (r *Repository) ConfirmReceive(userID uint64, orderID uint64) error {
	return r.db.Transaction(func(tx *gorm.DB) error {
		var order model.RentalOrder
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil {
			return err
		}
		if order.RenterID != userID {
			return ErrPermissionDenied
		}
		if order.Status != "pending_handoff" || order.HandoffStatus != "pending_renter_confirm" {
			return ErrOrderCannotReceive
		}
		now := time.Now()
		if err := tx.Model(&model.HandoffRecord{}).
			Where("order_id = ? AND type = ?", order.ID, "owner_handoff").
			Update("confirmed_by_renter_at", now).Error; err != nil {
			return err
		}
		order.Status = "renting"
		order.HandoffStatus = "received"
		durationHours := orderDurationHours(order)
		order.EstimatedDurationHours = durationHours
		order.RentedAt = &now
		orderID := order.ID
		if err := notification.Append(tx, notification.Entry{
			UserID:  order.OwnerID,
			Type:    "handoff",
			Title:   "租客已确认收号",
			Content: "订单已进入使用中。",
			BizType: "order",
			BizID:   &orderID,
		}); err != nil {
			return err
		}
		return tx.Save(&order).Error
	})
}

func (r *Repository) HandoffRecords(userID uint64, orderID uint64) ([]HandoffRecordDTO, error) {
	var order model.RentalOrder
	if err := r.db.Where("id = ? AND (renter_id = ? OR owner_id = ?)", orderID, userID, userID).First(&order).Error; err != nil {
		return nil, err
	}
	var records []model.HandoffRecord
	if err := r.db.Where("order_id = ?", orderID).Order("id ASC").Find(&records).Error; err != nil {
		return nil, err
	}
	items := make([]HandoffRecordDTO, 0, len(records))
	for _, record := range records {
		items = append(items, toHandoffDTO(record))
	}
	return items, nil
}

func (r *Repository) SubmitReturn(userID uint64, orderID uint64, req SubmitReturnRequest) (*HandoffRecordDTO, error) {
	return r.SubmitCheckout(userID, orderID, SubmitCheckoutRequest{Content: req.Content})
}

func (r *Repository) SubmitCheckout(userID uint64, orderID uint64, req SubmitCheckoutRequest) (*HandoffRecordDTO, error) {
	var recordID uint64
	err := r.db.Transaction(func(tx *gorm.DB) error {
		var order model.RentalOrder
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil {
			return err
		}
		if order.RenterID != userID {
			return ErrPermissionDenied
		}
		if (order.Status != "renting" && order.Status != "overdue") || (order.HandoffStatus != "received" && order.HandoffStatus != "return_overdue") {
			return ErrCheckoutCannotSubmit
		}
		hasOpen, err := hasOpenCheckout(tx, order.ID)
		if err != nil {
			return err
		}
		if hasOpen {
			return ErrCheckoutCannotSubmit
		}
		record := model.HandoffRecord{
			OrderID:    order.ID,
			FromUserID: order.RenterID,
			ToUserID:   order.OwnerID,
			Type:       "renter_checkout",
			Content:    req.Content,
		}
		if err := tx.Create(&record).Error; err != nil {
			return err
		}
		checkout, err := buildCheckout(order, order.RenterID, "submitted", req.Content, req.EvidenceURLS, req.ConsumableAmount, req.CoinConsumedM, req.OtherAmount, 0, false)
		if err != nil {
			return err
		}
		if err := tx.Create(&checkout).Error; err != nil {
			return err
		}
		order.Status = "pending_checkout_confirm"
		order.HandoffStatus = "pending_owner_checkout"
		order.SettlementStatus = "pending"
		orderID := order.ID
		if err := notification.Append(tx, notification.Entry{
			UserID:  order.OwnerID,
			Type:    "checkout",
			Title:   "租客已发起结账",
			Content: "请检查账号状态和消耗明细，确认无误后完成结算。",
			BizType: "order",
			BizID:   &orderID,
		}); err != nil {
			return err
		}
		if err := tx.Save(&order).Error; err != nil {
			return err
		}
		recordID = record.ID
		return nil
	})
	if err != nil {
		return nil, err
	}
	return r.findHandoffRecord(recordID)
}

func (r *Repository) ConfirmReturn(userID uint64, orderID uint64) error {
	return r.ConfirmCheckout(userID, orderID)
}

func (r *Repository) ConfirmCheckout(userID uint64, orderID uint64) error {
	var refund *refundAction
	err := r.db.Transaction(func(tx *gorm.DB) error {
		var order model.RentalOrder
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil {
			return err
		}
		if order.OwnerID != userID {
			return ErrPermissionDenied
		}
		if order.Status != "pending_checkout_confirm" || order.HandoffStatus != "pending_owner_checkout" {
			return ErrCheckoutCannotConfirm
		}
		var checkout model.OrderCheckout
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("order_id = ? AND status = ?", order.ID, "submitted").
			Order("id DESC").
			First(&checkout).Error; err != nil {
			return err
		}
		now := time.Now()
		if err := tx.Model(&model.HandoffRecord{}).
			Where("order_id = ? AND type = ?", order.ID, "renter_checkout").
			Update("confirmed_by_owner_at", now).Error; err != nil {
			return err
		}
		checkout.Status = "accepted"
		checkout.OwnerAdjustedAt = &now
		action, err := r.finalizeCheckout(tx, &order, &checkout, "号主已确认结账，订单完成。")
		refund = action
		return err
	})
	if err != nil {
		return err
	}
	r.startRefundBestEffort(refund)
	return nil
}

func (r *Repository) CounterCheckout(userID uint64, orderID uint64, req CounterCheckoutRequest) (*CheckoutDTO, error) {
	var checkoutID uint64
	err := r.db.Transaction(func(tx *gorm.DB) error {
		var order model.RentalOrder
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil {
			return err
		}
		if order.OwnerID != userID {
			return ErrPermissionDenied
		}
		if order.Status != "pending_checkout_confirm" || order.HandoffStatus != "pending_owner_checkout" {
			return ErrCheckoutCannotCounter
		}
		var checkout model.OrderCheckout
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("order_id = ? AND status = ?", order.ID, "submitted").
			Order("id DESC").
			First(&checkout).Error; err != nil {
			return err
		}
		next, err := buildCheckout(order, checkout.InitiatedBy, "countered", checkout.Content, req.EvidenceURLS, req.ConsumableAmount, req.CoinConsumedM, req.OtherAmount, req.DepositDeductAmount, true)
		if err != nil {
			return err
		}
		now := time.Now()
		checkout.Status = "countered"
		checkout.ConsumableAmount = next.ConsumableAmount
		checkout.CoinConsumedM = next.CoinConsumedM
		checkout.OtherAmount = next.OtherAmount
		checkout.DepositDeductAmount = next.DepositDeductAmount
		checkout.RenterRefundAmount = next.RenterRefundAmount
		checkout.OwnerIncomeAmount = next.OwnerIncomeAmount
		checkout.OwnerAdjustmentReason = req.Reason
		checkout.OwnerAdjustedAt = &now
		checkout.EvidenceURLS = next.EvidenceURLS
		order.Status = "pending_checkout_accept"
		order.HandoffStatus = "pending_renter_checkout"
		order.SettlementStatus = "pending"
		orderID := order.ID
		if err := notification.Append(tx, notification.Entry{
			UserID:  order.RenterID,
			Type:    "checkout",
			Title:   "号主已修改结账金额",
			Content: "请核对号主修正的消耗和结算金额。同意后订单完成；不同意可发起争议。",
			BizType: "order",
			BizID:   &orderID,
		}); err != nil {
			return err
		}
		if err := tx.Save(&order).Error; err != nil {
			return err
		}
		if err := tx.Save(&checkout).Error; err != nil {
			return err
		}
		checkoutID = checkout.ID
		return nil
	})
	if err != nil {
		return nil, err
	}
	checkout, err := r.findCheckout(checkoutID)
	if err != nil {
		return nil, err
	}
	dto := toCheckoutDTOForUser(*checkout, userID, model.RentalOrder{
		OwnerID:         userID,
		RentAmount:      checkout.RentAmount,
		OwnerRentAmount: checkout.OwnerRentAmount,
		DepositAmount:   checkout.DepositAmount,
	})
	return &dto, nil
}

func (r *Repository) AcceptCheckout(userID uint64, orderID uint64) error {
	var refund *refundAction
	err := r.db.Transaction(func(tx *gorm.DB) error {
		var order model.RentalOrder
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil {
			return err
		}
		if order.RenterID != userID {
			return ErrPermissionDenied
		}
		if order.Status != "pending_checkout_accept" || order.HandoffStatus != "pending_renter_checkout" {
			return ErrCheckoutCannotConfirm
		}
		var checkout model.OrderCheckout
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("order_id = ? AND status = ?", order.ID, "countered").
			Order("id DESC").
			First(&checkout).Error; err != nil {
			return err
		}
		now := time.Now()
		checkout.Status = "accepted"
		checkout.RenterConfirmedAt = &now
		action, err := r.finalizeCheckout(tx, &order, &checkout, "租客已确认修正结账，订单完成。")
		refund = action
		return err
	})
	if err != nil {
		return err
	}
	r.startRefundBestEffort(refund)
	return nil
}

func (r *Repository) ListForUser(userID uint64) ([]OrderDTO, error) {
	var rows []orderRow
	err := r.baseQuery().
		Where("o.renter_id = ? OR o.owner_id = ?", userID, userID).
		Order("o.id DESC").
		Scan(&rows).Error
	if err != nil {
		return nil, err
	}
	items := make([]OrderDTO, 0, len(rows))
	for _, row := range rows {
		dto := row.toDTOForUser(userID)
		if shouldAttachCheckout(row.Status) {
			dto.Checkout = r.latestCheckoutDTOForUser(row.ID, userID, row.RentalOrder)
		}
		items = append(items, dto)
	}
	return items, nil
}

func (r *Repository) ListAdmin() ([]OrderDTO, error) {
	var rows []orderRow
	err := r.adminQuery().
		Order("o.id DESC").
		Limit(200).
		Scan(&rows).Error
	if err != nil {
		return nil, err
	}
	items := make([]OrderDTO, 0, len(rows))
	for _, row := range rows {
		items = append(items, row.toAdminDTO())
	}
	return items, nil
}

func (r *Repository) FindAdmin(orderID uint64) (*OrderDTO, error) {
	var row orderRow
	if err := r.adminQuery().Where("o.id = ?", orderID).First(&row).Error; err != nil {
		return nil, err
	}
	dto := row.toAdminDTO()
	dto.Checkout = r.latestCheckoutAdminDTO(orderID)
	return &dto, nil
}

func (r *Repository) HandoffRecordsAdmin(orderID uint64) ([]HandoffRecordDTO, error) {
	var order model.RentalOrder
	if err := r.db.First(&order, orderID).Error; err != nil {
		return nil, err
	}
	var records []model.HandoffRecord
	if err := r.db.Where("order_id = ?", orderID).Order("id ASC").Find(&records).Error; err != nil {
		return nil, err
	}
	items := make([]HandoffRecordDTO, 0, len(records))
	for _, record := range records {
		items = append(items, toHandoffDTO(record))
	}
	return items, nil
}

func (r *Repository) AdminClose(adminID uint64, orderID uint64, req AdminActionRequest, meta AuditMeta) error {
	var refund *refundAction
	err := r.db.Transaction(func(tx *gorm.DB) error {
		order, listing, account, err := r.findOrderAssetsForAdminUpdate(tx, orderID)
		if err != nil {
			return err
		}
		if isTerminalStatus(order.Status) {
			return ErrOrderCannotComplete
		}
		beforeOrderStatus := order.Status
		beforeHandoffStatus := order.HandoffStatus
		beforeSettlementStatus := order.SettlementStatus
		beforeListingStatus := listing.Status
		beforeAccountStatus := account.Status
		now := time.Now()
		order.Status = "closed"
		order.HandoffStatus = "admin_closed"
		order.SettlementStatus = "closed"
		order.SettledAt = &now
		listing.Status = "offline"
		listing.InTransaction = false
		account.Status = "offline"
		if beforeOrderStatus != "pending_payment" {
			totalCent := int64(math.Round((order.RentAmount + order.DepositAmount) * 100))
			action, err := r.prepareRefund(order, totalCent, "admin_close_refund", "客服关闭订单原路退款")
			if err != nil {
				return err
			}
			refund = action
		}
		if err := notification.Append(tx,
			notification.Entry{
				UserID:  order.RenterID,
				Type:    "order_admin",
				Title:   "订单已由客服关闭",
				Content: "客服已关闭订单，退款将原路退回您的支付账户。原因：" + req.Reason,
				BizType: "order",
				BizID:   &order.ID,
			},
			notification.Entry{
				UserID:  order.OwnerID,
				Type:    "order_admin",
				Title:   "订单已由客服关闭",
				Content: "客服已关闭订单，关联商品已下架。原因：" + req.Reason,
				BizType: "order",
				BizID:   &order.ID,
			},
		); err != nil {
			return err
		}
		if err := appendAuditLog(tx, adminID, "order.admin_close", "order", order.ID, meta, map[string]any{
			"order_id":                 order.ID,
			"order_no":                 order.OrderNo,
			"listing_id":               order.ListingID,
			"account_id":               order.AccountID,
			"reason":                   req.Reason,
			"before_order_status":      beforeOrderStatus,
			"after_order_status":       order.Status,
			"before_handoff_status":    beforeHandoffStatus,
			"after_handoff_status":     order.HandoffStatus,
			"before_settlement_status": beforeSettlementStatus,
			"after_settlement_status":  order.SettlementStatus,
			"before_listing_status":    beforeListingStatus,
			"after_listing_status":     listing.Status,
			"before_account_status":    beforeAccountStatus,
			"after_account_status":     account.Status,
		}); err != nil {
			return err
		}
		if err := tx.Save(order).Error; err != nil {
			return err
		}
		if err := tx.Save(listing).Error; err != nil {
			return err
		}
		return tx.Save(account).Error
	})
	if err != nil {
		return err
	}
	r.startRefundBestEffort(refund)
	return nil
}

func (r *Repository) AdminMarkAbnormal(adminID uint64, orderID uint64, req AdminActionRequest, meta AuditMeta) error {
	return r.db.Transaction(func(tx *gorm.DB) error {
		order, listing, account, err := r.findOrderAssetsForAdminUpdate(tx, orderID)
		if err != nil {
			return err
		}
		if isTerminalStatus(order.Status) {
			return ErrOrderCannotComplete
		}
		beforeOrderStatus := order.Status
		beforeHandoffStatus := order.HandoffStatus
		beforeListingStatus := listing.Status
		beforeAccountStatus := account.Status
		order.Status = "abnormal"
		order.HandoffStatus = "admin_abnormal"
		listing.Status = "abnormal"
		listing.InTransaction = false
		account.Status = "abnormal"
		if err := notification.Append(tx,
			notification.Entry{
				UserID:  order.RenterID,
				Type:    "order_admin",
				Title:   "订单已被标记异常",
				Content: "客服已将订单标记为异常，请等待进一步处理。原因：" + req.Reason,
				BizType: "order",
				BizID:   &order.ID,
			},
			notification.Entry{
				UserID:  order.OwnerID,
				Type:    "order_admin",
				Title:   "订单已被标记异常",
				Content: "客服已将订单标记为异常，关联商品暂不可出租。原因：" + req.Reason,
				BizType: "order",
				BizID:   &order.ID,
			},
		); err != nil {
			return err
		}
		if err := appendAuditLog(tx, adminID, "order.mark_abnormal", "order", order.ID, meta, map[string]any{
			"order_id":              order.ID,
			"order_no":              order.OrderNo,
			"listing_id":            order.ListingID,
			"account_id":            order.AccountID,
			"reason":                req.Reason,
			"before_order_status":   beforeOrderStatus,
			"after_order_status":    order.Status,
			"before_handoff_status": beforeHandoffStatus,
			"after_handoff_status":  order.HandoffStatus,
			"before_listing_status": beforeListingStatus,
			"after_listing_status":  listing.Status,
			"before_account_status": beforeAccountStatus,
			"after_account_status":  account.Status,
		}); err != nil {
			return err
		}
		if err := tx.Save(order).Error; err != nil {
			return err
		}
		if err := tx.Save(listing).Error; err != nil {
			return err
		}
		return tx.Save(account).Error
	})
}

// AdminRefund 触发后台人工退款，退款由 payment 模块走渠道原路退回。
func (r *Repository) AdminRefund(orderID uint64) (*RefundStatusDTO, error) {
	var order model.RentalOrder
	if err := r.db.First(&order, orderID).Error; err != nil {
		return nil, err
	}
	if order.RefundStatus == "refunded" {
		return r.buildRefundStatusDTO(&order), nil
	}
	if r.refundFunc == nil {
		return nil, ErrDependencyUnavailable
	}
	totalCent := int64(math.Round((order.RentAmount + order.DepositAmount) * 100))
	if totalCent <= 0 {
		return nil, ErrInvalidCheckoutAmount
	}
	status, err := r.refundFunc(orderID, totalCent, "admin_refund", "后台人工退款")
	if err != nil {
		return nil, err
	}
	// 重新读取订单，拿到 payment 模块更新后的退款字段。
	if err := r.db.First(&order, orderID).Error; err != nil {
		return nil, err
	}
	dto := r.buildRefundStatusDTO(&order)
	if status != "" {
		dto.RefundStatus = status
	}
	return dto, nil
}

// AdminRefundStatus 查询订单退款状态。
func (r *Repository) AdminRefundStatus(orderID uint64) (*RefundStatusDTO, error) {
	var order model.RentalOrder
	if err := r.db.First(&order, orderID).Error; err != nil {
		return nil, err
	}
	return r.buildRefundStatusDTO(&order), nil
}

func (r *Repository) buildRefundStatusDTO(order *model.RentalOrder) *RefundStatusDTO {
	return &RefundStatusDTO{
		OrderID:          order.ID,
		OrderNo:          order.OrderNo,
		RefundStatus:     order.RefundStatus,
		RefundAmountCent: order.RefundAmountCent,
		RefundedAt:       order.RefundedAt,
		TotalAmount:      order.RentAmount + order.DepositAmount,
	}
}

func (r *Repository) FindForUser(userID uint64, orderID uint64) (*OrderDTO, error) {
	var row orderRow
	if err := r.baseQuery().
		Where("o.id = ? AND (o.renter_id = ? OR o.owner_id = ?)", orderID, userID, userID).
		First(&row).Error; err != nil {
		return nil, err
	}
	dto := row.toDTOForUser(userID)
	dto.Checkout = r.latestCheckoutDTOForUser(orderID, userID, row.RentalOrder)
	return &dto, nil
}

func (r *Repository) finalizeCheckout(tx *gorm.DB, order *model.RentalOrder, checkout *model.OrderCheckout, renterContent string) (*refundAction, error) {
	var listing model.RentalListing
	if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&listing, order.ListingID).Error; err != nil {
		return nil, err
	}
	var account model.GameAccount
	if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&account, order.AccountID).Error; err != nil {
		return nil, err
	}
	now := time.Now()
	order.Status = "completed"
	order.HandoffStatus = "returned"
	order.SettlementStatus = "settled"
	order.SettledAt = &now
	order.OwnerSettledAt = &now
	archiveListingAfterCheckout(&listing, &account)
	orderID := order.ID
	settlement := buildCheckoutSettlement(*order, checkout)

	// 卖家收入进入站内钱包；租客资金不进入站内钱包。
	var ownerEntries []wallet.Entry
	if settlement.OwnerRentIncome > 0 {
		ownerEntries = append(ownerEntries, wallet.Entry{
			UserID:      order.OwnerID,
			OrderID:     &orderID,
			Direction:   "in",
			Amount:      settlement.OwnerRentIncome,
			BalanceType: "available",
			BizType:     "owner_income",
			BizNo:       order.OrderNo,
			Remark:      "订单结账租金收入",
		})
	}
	if settlement.DepositCompensation > 0 {
		ownerEntries = append(ownerEntries, wallet.Entry{
			UserID:      order.OwnerID,
			OrderID:     &orderID,
			Direction:   "in",
			Amount:      settlement.DepositCompensation,
			BalanceType: "available",
			BizType:     "deposit_compensation",
			BizNo:       order.OrderNo,
			Remark:      "订单结账押金赔付",
		})
	}
	if len(ownerEntries) > 0 {
		if err := wallet.AppendEntries(tx, ownerEntries...); err != nil {
			return nil, err
		}
	}

	var refund *refundAction
	renterRefundTotal := settlement.RentRefund + settlement.DepositRefund
	if renterRefundTotal > 0 {
		refundCent := int64(math.Round(renterRefundTotal * 100))
		action, err := r.prepareRefund(order, refundCent, "checkout_refund", "结账退款原路退还")
		if err != nil {
			return nil, err
		}
		refund = action
	}

	checkout.RentAmount = settlement.ActualRentAmount
	checkout.OwnerRentAmount = settlement.OwnerRentIncome
	checkout.PlatformFee = settlement.PlatformFee
	checkout.RenterRefundAmount = settlement.RenterRefund
	checkout.OwnerIncomeAmount = settlement.OwnerIncome
	if err := notification.Append(tx,
		notification.Entry{
			UserID:  order.RenterID,
			Type:    "settlement",
			Title:   "订单已完成",
			Content: renterContent,
			BizType: "order",
			BizID:   &orderID,
		},
		notification.Entry{
			UserID:  order.OwnerID,
			Type:    "settlement",
			Title:   "订单已完成",
			Content: "订单已完成，结账金额已入账。",
			BizType: "order",
			BizID:   &orderID,
		},
	); err != nil {
		return nil, err
	}
	if err := tx.Save(order).Error; err != nil {
		return nil, err
	}
	if err := tx.Save(checkout).Error; err != nil {
		return nil, err
	}
	if err := tx.Save(&listing).Error; err != nil {
		return nil, err
	}
	if err := tx.Save(&account).Error; err != nil {
		return nil, err
	}
	return refund, nil
}

// 完成后的账号先下架，避免已完成订单对应的账号重新出现在公开首页。
func archiveListingAfterCheckout(listing *model.RentalListing, account *model.GameAccount) {
	listing.Status = "offline"
	listing.InTransaction = false
	listing.PublishedAt = nil
	account.Status = "offline"
}

func (r *Repository) prepareRefund(order *model.RentalOrder, amountCent int64, bizType string, remark string) (*refundAction, error) {
	if amountCent <= 0 {
		return nil, nil
	}
	if r.refundFunc == nil {
		return nil, ErrDependencyUnavailable
	}
	order.RefundStatus = "pending"
	order.RefundAmountCent = amountCent
	order.RefundedAt = nil
	return &refundAction{
		OrderID:          order.ID,
		RefundAmountCent: amountCent,
		BizType:          bizType,
		Remark:           remark,
	}, nil
}

func (r *Repository) startRefundBestEffort(action *refundAction) {
	if action == nil || r.refundFunc == nil {
		return
	}
	if _, err := r.refundFunc(action.OrderID, action.RefundAmountCent, action.BizType, action.Remark); err != nil {
		log.Printf("[order] start refund failed order_id=%d biz_type=%s amount_cent=%d err=%v", action.OrderID, action.BizType, action.RefundAmountCent, err)
	}
}

func (r *Repository) findOrderAssetsForAdminUpdate(tx *gorm.DB, orderID uint64) (*model.RentalOrder, *model.RentalListing, *model.GameAccount, error) {
	var order model.RentalOrder
	if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, orderID).Error; err != nil {
		return nil, nil, nil, err
	}
	var listing model.RentalListing
	if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&listing, order.ListingID).Error; err != nil {
		return nil, nil, nil, err
	}
	var account model.GameAccount
	if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&account, order.AccountID).Error; err != nil {
		return nil, nil, nil, err
	}
	return &order, &listing, &account, nil
}

func isTerminalStatus(status string) bool {
	return status == "completed" || status == "cancelled" || status == "closed"
}

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

func (r *Repository) findHandoffRecord(id uint64) (*HandoffRecordDTO, error) {
	var record model.HandoffRecord
	if err := r.db.First(&record, id).Error; err != nil {
		return nil, err
	}
	dto := toHandoffDTO(record)
	return &dto, nil
}

func pendingPaymentTimeoutMinutes(tx *gorm.DB) int {
	var row model.SystemConfig
	err := tx.Where("`key` = ?", "order.pending_payment_timeout_minutes").First(&row).Error
	if err != nil {
		return defaultPendingPaymentTimeoutMinutes
	}
	value, err := strconv.Atoi(row.Value)
	if err != nil || value < 0 {
		return defaultPendingPaymentTimeoutMinutes
	}
	return value
}

func buildCheckout(order model.RentalOrder, initiatedBy uint64, status string, content string, evidenceURLS []string, consumableAmount float64, coinConsumedM float64, otherAmount float64, explicitDeduct float64, useExplicitDeduct bool) (model.OrderCheckout, error) {
	if consumableAmount < 0 || coinConsumedM < 0 || otherAmount < 0 || explicitDeduct < 0 {
		return model.OrderCheckout{}, ErrInvalidCheckoutAmount
	}
	consumableAmount = roundMoney(consumableAmount)
	otherAmount = roundMoney(otherAmount)
	explicitDeduct = roundMoney(explicitDeduct)
	deductAmount := otherAmount
	if useExplicitDeduct {
		deductAmount = explicitDeduct
	}
	if deductAmount > order.DepositAmount {
		return model.OrderCheckout{}, ErrInvalidCheckoutAmount
	}
	settlement := calculateCheckoutSettlement(order, consumableAmount, roundQuantity(coinConsumedM), deductAmount)
	evidence, err := marshalStringList(evidenceURLS)
	if err != nil {
		return model.OrderCheckout{}, err
	}
	return model.OrderCheckout{
		OrderID:             order.ID,
		InitiatedBy:         initiatedBy,
		Status:              status,
		RentAmount:          settlement.ActualRentAmount,
		OwnerRentAmount:     settlement.OwnerRentIncome,
		PlatformFee:         settlement.PlatformFee,
		DepositAmount:       order.DepositAmount,
		ConsumableAmount:    consumableAmount,
		CoinConsumedM:       roundQuantity(coinConsumedM),
		OtherAmount:         otherAmount,
		DepositDeductAmount: roundMoney(deductAmount),
		RenterRefundAmount:  settlement.RenterRefund,
		OwnerIncomeAmount:   settlement.OwnerIncome,
		Content:             content,
		EvidenceURLS:        evidence,
	}, nil
}

func buildCheckoutSettlement(order model.RentalOrder, checkout *model.OrderCheckout) checkoutSettlement {
	return calculateCheckoutSettlement(order, checkout.ConsumableAmount, checkout.CoinConsumedM, checkout.DepositDeductAmount)
}

func calculateCheckoutSettlement(order model.RentalOrder, consumableAmount float64, coinConsumedM float64, depositDeductAmount float64) checkoutSettlement {
	buyerCoinBasePrice := readOrderSnapshotPrice(order.AccountSnapshot, "buyer_coin_base_price")
	if buyerCoinBasePrice <= 0 || buyerCoinBasePrice > order.RentAmount {
		buyerCoinBasePrice = order.RentAmount
	}
	sellerCoinBasePrice := readOrderSnapshotPrice(order.AccountSnapshot, "seller_coin_base_price")
	if sellerCoinBasePrice <= 0 || sellerCoinBasePrice > order.OwnerRentAmount {
		sellerCoinBasePrice = order.OwnerRentAmount
	}
	prepaidConsumablePrice := readOrderSnapshotPrice(order.AccountSnapshot, "consumable_price")
	if prepaidConsumablePrice <= 0 || prepaidConsumablePrice > order.RentAmount-buyerCoinBasePrice {
		prepaidConsumablePrice = maxMoney(order.RentAmount-buyerCoinBasePrice, 0)
	}
	prepaidOwnerConsumablePrice := maxMoney(order.OwnerRentAmount-sellerCoinBasePrice, 0)
	totalCoinM := readOrderSnapshotCoinM(order.AccountSnapshot)
	coinUseRatio := 1.0
	if totalCoinM > 0 {
		coinUseRatio = minRatio(maxRatio(roundQuantity(coinConsumedM)/totalCoinM, 0), 1)
	}
	usedBuyerCoinPrice := roundMoney(buyerCoinBasePrice * coinUseRatio)
	usedOwnerCoinPrice := roundMoney(sellerCoinBasePrice * coinUseRatio)
	usedBuyerConsumablePrice := minMoney(roundMoney(consumableAmount), prepaidConsumablePrice)
	consumableUseRatio := 1.0
	if prepaidConsumablePrice > 0 {
		consumableUseRatio = minRatio(maxRatio(usedBuyerConsumablePrice/prepaidConsumablePrice, 0), 1)
	}
	usedOwnerConsumablePrice := roundMoney(prepaidOwnerConsumablePrice * consumableUseRatio)
	actualRentAmount := minMoney(roundMoney(usedBuyerCoinPrice+usedBuyerConsumablePrice), order.RentAmount)
	ownerRentIncome := minMoney(roundMoney(usedOwnerCoinPrice+usedOwnerConsumablePrice), order.OwnerRentAmount)
	depositCompensation := minMoney(roundMoney(depositDeductAmount), order.DepositAmount)
	rentRefund := maxMoney(order.RentAmount-actualRentAmount, 0)
	depositRefund := maxMoney(order.DepositAmount-depositCompensation, 0)
	return checkoutSettlement{
		OwnerRentIncome:     ownerRentIncome,
		DepositCompensation: depositCompensation,
		OwnerIncome:         roundMoney(ownerRentIncome + depositCompensation),
		RentRefund:          rentRefund,
		DepositRefund:       depositRefund,
		RenterRefund:        roundMoney(rentRefund + depositRefund),
		PlatformFee:         maxMoney(actualRentAmount-ownerRentIncome, 0),
		ActualRentAmount:    actualRentAmount,
	}
}

func readOrderSnapshotCoinM(raw datatypes.JSON) float64 {
	if len(raw) == 0 {
		return 0
	}
	var snapshot map[string]any
	if err := json.Unmarshal(raw, &snapshot); err != nil {
		return 0
	}
	return roundQuantity(readJSONNumber(snapshot["haf_coin_amount"]) / 1000000)
}

func marshalStringList(items []string) (datatypes.JSON, error) {
	if items == nil {
		items = []string{}
	}
	raw, err := json.Marshal(items)
	return datatypes.JSON(raw), err
}

func decodeStringList(raw datatypes.JSON) []string {
	if len(raw) == 0 {
		return []string{}
	}
	var items []string
	if err := json.Unmarshal(raw, &items); err != nil {
		return []string{}
	}
	return items
}

// roundMoney 使用统一的角精度（0.1元）
func roundMoney(value float64) float64 {
	return money.Round(value)
}

func roundQuantity(value float64) float64 {
	return math.Round(value*100) / 100
}

// minMoney 返回较小金额（角精度）
func minMoney(a float64, b float64) float64 {
	return money.Min(a, b)
}

// maxMoney 返回较大金额（角精度）
func maxMoney(a float64, b float64) float64 {
	return money.Max(a, b)
}

func minRatio(a float64, b float64) float64 {
	if a < b {
		return a
	}
	return b
}

func maxRatio(a float64, b float64) float64 {
	if a > b {
		return a
	}
	return b
}

func hasOpenCheckout(tx *gorm.DB, orderID uint64) (bool, error) {
	var count int64
	err := tx.Model(&model.OrderCheckout{}).
		Where("order_id = ? AND status IN ?", orderID, []string{"submitted", "countered", "accepted", "disputed"}).
		Count(&count).Error
	return count > 0, err
}

func (r *Repository) latestCheckoutAdminDTO(orderID uint64) *CheckoutDTO {
	var checkout model.OrderCheckout
	if err := r.db.Where("order_id = ?", orderID).Order("id DESC").First(&checkout).Error; err != nil {
		return nil
	}
	dto := toCheckoutAdminDTO(checkout)
	return &dto
}

func shouldAttachCheckout(status string) bool {
	switch status {
	case "pending_checkout_confirm", "pending_checkout_accept", "checkout_disputing", "completed":
		return true
	default:
		return false
	}
}

func (r *Repository) latestCheckoutDTOForUser(orderID uint64, userID uint64, order model.RentalOrder) *CheckoutDTO {
	var checkout model.OrderCheckout
	if err := r.db.Where("order_id = ?", orderID).Order("id DESC").First(&checkout).Error; err != nil {
		return nil
	}
	dto := toCheckoutDTOForUser(checkout, userID, order)
	return &dto
}

func (r *Repository) findCheckout(id uint64) (*model.OrderCheckout, error) {
	var checkout model.OrderCheckout
	if err := r.db.First(&checkout, id).Error; err != nil {
		return nil, err
	}
	return &checkout, nil
}

func (r *Repository) baseQuery() *gorm.DB {
	return r.db.Table("rental_orders AS o").
		Select("o.*, a.title, a.server_region, a.login_platform").
		Joins("JOIN game_accounts AS a ON a.id = o.account_id")
}

func (r *Repository) adminQuery() *gorm.DB {
	return r.db.Table("rental_orders AS o").
		Select("o.*, a.title, a.server_region, a.login_platform, owner.phone AS owner_phone, renter.phone AS renter_phone").
		Joins("JOIN game_accounts AS a ON a.id = o.account_id").
		Joins("JOIN users AS owner ON owner.id = o.owner_id").
		Joins("JOIN users AS renter ON renter.id = o.renter_id")
}

type orderRow struct {
	model.RentalOrder
	Title         string
	ServerRegion  string
	LoginPlatform string
	OwnerPhone    string
	RenterPhone   string
}

func (row orderRow) toAdminDTO() OrderDTO {
	rentedAt := row.RentedAt
	durationHours := orderDurationHours(row.RentalOrder)
	rentAmount := row.RentAmount
	ownerRentAmount := row.OwnerRentAmount
	platformFee := row.PlatformFee
	return OrderDTO{
		ID:                     row.ID,
		OrderNo:                row.OrderNo,
		ListingID:              row.ListingID,
		AccountID:              row.AccountID,
		OwnerID:                row.OwnerID,
		RenterID:               row.RenterID,
		OwnerPhone:             row.OwnerPhone,
		RenterPhone:            row.RenterPhone,
		Title:                  row.Title,
		ServerRegion:           row.ServerRegion,
		LoginPlatform:          row.LoginPlatform,
		RentedAt:               rentedAt,
		EstimatedDurationHours: durationHours,
		PriceRole:              "admin",
		DisplayAmount:          row.RentAmount,
		RentAmount:             &rentAmount,
		OwnerRentAmount:        &ownerRentAmount,
		DepositAmount:          row.DepositAmount,
		PlatformFee:            &platformFee,
		AccountSnapshot:        row.AccountSnapshot,
		Status:                 row.Status,
		HandoffStatus:          row.HandoffStatus,
		SettlementStatus:       row.SettlementStatus,
		CreatedAt:              row.CreatedAt,
		UpdatedAt:              row.UpdatedAt,
	}
}

func (row orderRow) toDTOForUser(userID uint64) OrderDTO {
	dto := row.toAdminDTO()
	applyOrderPriceView(&dto, row.RentalOrder, userID)
	return dto
}

func toHandoffDTO(record model.HandoffRecord) HandoffRecordDTO {
	return HandoffRecordDTO{
		ID:                  record.ID,
		OrderID:             record.OrderID,
		FromUserID:          record.FromUserID,
		ToUserID:            record.ToUserID,
		Type:                record.Type,
		Content:             record.Content,
		ConfirmedByRenterAt: record.ConfirmedByRenterAt,
		ConfirmedByOwnerAt:  record.ConfirmedByOwnerAt,
		CreatedAt:           record.CreatedAt,
	}
}

func toCheckoutAdminDTO(checkout model.OrderCheckout) CheckoutDTO {
	rentAmount := checkout.RentAmount
	ownerRentAmount := checkout.OwnerRentAmount
	platformFee := checkout.PlatformFee
	renterRefundAmount := checkout.RenterRefundAmount
	ownerIncomeAmount := checkout.OwnerIncomeAmount
	return CheckoutDTO{
		ID:                    checkout.ID,
		OrderID:               checkout.OrderID,
		InitiatedBy:           checkout.InitiatedBy,
		Status:                checkout.Status,
		PriceRole:             "admin",
		DisplayAmount:         checkout.RentAmount,
		RentAmount:            &rentAmount,
		OwnerRentAmount:       &ownerRentAmount,
		PlatformFee:           &platformFee,
		DepositAmount:         checkout.DepositAmount,
		ConsumableAmount:      checkout.ConsumableAmount,
		CoinConsumedM:         checkout.CoinConsumedM,
		OtherAmount:           checkout.OtherAmount,
		DepositDeductAmount:   checkout.DepositDeductAmount,
		RenterRefundAmount:    &renterRefundAmount,
		OwnerIncomeAmount:     &ownerIncomeAmount,
		Content:               checkout.Content,
		EvidenceURLS:          decodeStringList(checkout.EvidenceURLS),
		OwnerAdjustmentReason: checkout.OwnerAdjustmentReason,
		OwnerAdjustedAt:       checkout.OwnerAdjustedAt,
		RenterConfirmedAt:     checkout.RenterConfirmedAt,
		RenterRejectedAt:      checkout.RenterRejectedAt,
		CreatedAt:             checkout.CreatedAt,
		UpdatedAt:             checkout.UpdatedAt,
	}
}

func toCheckoutDTOForUser(checkout model.OrderCheckout, userID uint64, order model.RentalOrder) CheckoutDTO {
	dto := toCheckoutAdminDTO(checkout)
	applyCheckoutPriceView(&dto, order, userID)
	return dto
}

func applyOrderPriceView(dto *OrderDTO, order model.RentalOrder, userID uint64) {
	if dto == nil {
		return
	}
	dto.PlatformFee = nil
	switch {
	case userID == order.OwnerID:
		ownerAmount := order.OwnerRentAmount
		if ownerAmount <= 0 {
			ownerAmount = order.RentAmount
		}
		dto.PriceRole = "owner"
		dto.DisplayAmount = ownerAmount
		dto.RentAmount = nil
		dto.OwnerRentAmount = &ownerAmount
		sanitizeOrderSnapshot(&dto.AccountSnapshot, "owner")
	case userID == order.RenterID:
		rentAmount := order.RentAmount
		dto.PriceRole = "renter"
		dto.DisplayAmount = rentAmount
		dto.RentAmount = &rentAmount
		dto.OwnerRentAmount = nil
		sanitizeOrderSnapshot(&dto.AccountSnapshot, "renter")
	default:
		dto.PriceRole = ""
		dto.DisplayAmount = 0
		dto.RentAmount = nil
		dto.OwnerRentAmount = nil
		sanitizeOrderSnapshot(&dto.AccountSnapshot, "")
	}
}

func applyCheckoutPriceView(dto *CheckoutDTO, order model.RentalOrder, userID uint64) {
	if dto == nil {
		return
	}
	ownerAmount := 0.0
	if dto.OwnerRentAmount != nil {
		ownerAmount = *dto.OwnerRentAmount
	}
	ownerIncomeAmount := 0.0
	if dto.OwnerIncomeAmount != nil {
		ownerIncomeAmount = *dto.OwnerIncomeAmount
	}
	rentAmount := 0.0
	if dto.RentAmount != nil {
		rentAmount = *dto.RentAmount
	}
	renterRefundAmount := 0.0
	if dto.RenterRefundAmount != nil {
		renterRefundAmount = *dto.RenterRefundAmount
	}
	dto.PlatformFee = nil
	dto.RenterRefundAmount = nil
	dto.OwnerIncomeAmount = nil
	switch {
	case userID == order.OwnerID:
		dto.PriceRole = "owner"
		dto.DisplayAmount = ownerAmount
		dto.RentAmount = nil
		dto.OwnerRentAmount = &ownerAmount
		dto.OwnerIncomeAmount = &ownerIncomeAmount
	case userID == order.RenterID:
		dto.PriceRole = "renter"
		dto.DisplayAmount = rentAmount
		dto.RentAmount = &rentAmount
		dto.OwnerRentAmount = nil
		dto.RenterRefundAmount = &renterRefundAmount
	default:
		dto.PriceRole = ""
		dto.DisplayAmount = 0
		dto.RentAmount = nil
		dto.OwnerRentAmount = nil
	}
}

func sanitizeOrderSnapshot(snapshot *datatypes.JSON, role string) {
	if snapshot == nil || len(*snapshot) == 0 {
		return
	}
	var payload map[string]any
	if err := json.Unmarshal(*snapshot, &payload); err != nil {
		return
	}
	rawSummary, ok := payload["asset_summary"]
	if !ok {
		return
	}
	var summary map[string]any
	switch typed := rawSummary.(type) {
	case map[string]any:
		summary = typed
	case string:
		if err := json.Unmarshal([]byte(typed), &summary); err != nil {
			return
		}
	default:
		raw, err := json.Marshal(typed)
		if err != nil || json.Unmarshal(raw, &summary) != nil {
			return
		}
	}
	breakdown, _ := summary["price_breakdown"].(map[string]any)
	if role == "owner" && breakdown != nil {
		if sellerRatio := readJSONNumber(breakdown["seller_ratio"]); sellerRatio > 0 {
			summary["publish_ratio"] = sellerRatio
		}
	}
	delete(summary, "price_breakdown")
	payload["asset_summary"] = summary
	raw, err := json.Marshal(payload)
	if err != nil {
		return
	}
	*snapshot = datatypes.JSON(raw)
}

func makeAccountSnapshot(account model.GameAccount) (datatypes.JSON, error) {
	payload := map[string]any{
		"account_id":       account.ID,
		"title":            account.Title,
		"game_name":        account.GameName,
		"server_region":    account.ServerRegion,
		"login_platform":   account.LoginPlatform,
		"rank_level":       account.RankLevel,
		"haf_coin_amount":  account.HafCoinAmount,
		"asset_summary":    account.AssetSummary,
		"season_tags":      account.SeasonTags,
		"screenshot_urls":  account.ScreenshotURLS,
		"snapshot_version": 1,
	}
	raw, err := json.Marshal(payload)
	return datatypes.JSON(raw), err
}

func newOrderNo() (string, error) {
	// 生成格式：RO + YYYYMMDDHHMMSS + 3位随机数
	// 例如：RO20260603123456789
	now := time.Now()

	// 时间部分：年月日时分秒 (14位)
	timeStr := now.Format("20060102150405")

	// 随机部分：3位数字 (000-999)
	buf := make([]byte, 2)
	if _, err := rand.Read(buf); err != nil {
		return "", err
	}
	// 将2字节转为0-999的数字
	randomNum := (int(buf[0])<<8 | int(buf[1])) % 1000

	return "RO" + timeStr + strconv.Itoa(1000 + randomNum)[1:], nil
}

func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizType string, bizID uint64, meta AuditMeta, detail map[string]any) error {
	return auditlog.Append(tx, auditlog.Entry{
		ActorType: "admin",
		ActorID:   actorID,
		Action:    action,
		BizType:   bizType,
		BizID:     &bizID,
		Meta:      meta,
		Detail:    detail,
	})
}

func IsNotFound(err error) bool {
	return errors.Is(err, gorm.ErrRecordNotFound)
}
