package admin

import (
	"fmt"
	"net/http"
	"time"

	"go.uber.org/zap"

	"bindbox-game/configs"
	"bindbox-game/internal/code"
	"bindbox-game/internal/pkg/core"
	"bindbox-game/internal/pkg/validation"
	"bindbox-game/internal/pkg/wechat"
	"bindbox-game/internal/repository/mysql/model"
	usersvc "bindbox-game/internal/service/user"
)

type listPayOrdersRequest struct {
	Page              int    `form:"page"`
	Current           int    `form:"current"`
	PageSize          int    `form:"page_size"`
	Size              int    `form:"size"`
	Status            *int32 `form:"status"`
	SourceType        *int32 `form:"source_type"`
	ExcludeSourceType *int32 `form:"exclude_source_type"`
	UserID            *int64 `form:"user_id"`
	OrderNo           string `form:"order_no"`
	IsConsumed        *int32 `form:"is_consumed"`
	StartDate         string `form:"start_date"`
	EndDate           string `form:"end_date"`
	PayStart          string `form:"pay_start"`
	PayEnd            string `form:"pay_end"`
}

type listPayOrdersResponse struct {
	Page     int              `json:"page"`
	PageSize int              `json:"page_size"`
	Total    int64            `json:"total"`
	List     []map[string]any `json:"list"`
}

func (h *handler) ListPayOrders() core.HandlerFunc {
	return func(ctx core.Context) {
		req := new(listPayOrdersRequest)
		rsp := new(listPayOrdersResponse)
		if err := ctx.ShouldBindForm(req); err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
			return
		}
		if req.Page <= 0 && req.Current > 0 {
			req.Page = req.Current
		}
		if req.PageSize <= 0 && req.Size > 0 {
			req.PageSize = req.Size
		}
		if req.Page <= 0 {
			req.Page = 1
		}
		if req.PageSize <= 0 {
			req.PageSize = 20
		}
		if req.PageSize > 100 {
			req.PageSize = 100
		}
		q := h.readDB.Orders.WithContext(ctx.RequestContext()).ReadDB()
		if req.Status != nil {
			q = q.Where(h.readDB.Orders.Status.Eq(*req.Status))
		}
		if req.SourceType != nil {
			q = q.Where(h.readDB.Orders.SourceType.Eq(*req.SourceType))
		}
		if req.ExcludeSourceType != nil {
			q = q.Not(h.readDB.Orders.SourceType.Eq(*req.ExcludeSourceType))
		}
		if req.UserID != nil {
			q = q.Where(h.readDB.Orders.UserID.Eq(*req.UserID))
		}
		if req.OrderNo != "" {
			q = q.Where(h.readDB.Orders.OrderNo.Eq(req.OrderNo))
		}
		if req.IsConsumed != nil {
			q = q.Where(h.readDB.Orders.IsConsumed.Eq(*req.IsConsumed))
		}
		if req.StartDate != "" {
			if t, err := time.Parse("2006-01-02", req.StartDate); err == nil {
				q = q.Where(h.readDB.Orders.CreatedAt.Gte(t))
			}
		}
		if req.EndDate != "" {
			if t, err := time.Parse("2006-01-02", req.EndDate); err == nil {
				t = t.Add(24 * time.Hour).Add(-time.Second)
				q = q.Where(h.readDB.Orders.CreatedAt.Lte(t))
			}
		}
		if req.PayStart != "" {
			if t, err := time.Parse(time.RFC3339, req.PayStart); err == nil {
				q = q.Where(h.readDB.Orders.PaidAt.Gte(t))
			}
		}
		if req.PayEnd != "" {
			if t, err := time.Parse(time.RFC3339, req.PayEnd); err == nil {
				q = q.Where(h.readDB.Orders.PaidAt.Lte(t))
			}
		}
		total, err := q.Count()
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 21001, err.Error()))
			return
		}
		rows, err := q.Order(h.readDB.Orders.ID.Desc()).Offset((req.Page - 1) * req.PageSize).Limit(req.PageSize).Find()
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 21002, err.Error()))
			return
		}
		var pointsRate int64 = 1
		if cfgRate, _ := h.readDB.SystemConfigs.WithContext(ctx.RequestContext()).Where(h.readDB.SystemConfigs.ConfigKey.Eq("points_exchange_per_cent")).First(); cfgRate != nil {
			var r int64
			_, _ = fmt.Sscanf(cfgRate.ConfigValue, "%d", &r)
			if r > 0 {
				pointsRate = r
			}
		}

		// 批量查询优惠券和道具卡信息
		userCouponIDs := make([]int64, 0)
		userItemCardIDs := make([]int64, 0)
		// 收集订单ID用于查询 OrderCoupons
		orderIDs := make([]int64, 0, len(rows))
		for _, o := range rows {
			orderIDs = append(orderIDs, o.ID)
			if o.CouponID > 0 {
				userCouponIDs = append(userCouponIDs, o.CouponID)
			}
			if o.ItemCardID > 0 {
				userItemCardIDs = append(userItemCardIDs, o.ItemCardID)
			}
		}

		couponMap := make(map[int64]map[string]any)
		// orderID -> userCouponID -> appliedAmount
		appliedAmountMap := make(map[int64]map[int64]int64)

		if len(userCouponIDs) > 0 {
			userCoupons, _ := h.readDB.UserCoupons.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.UserCoupons.ID.In(userCouponIDs...)).Find()
			var sysCouponIDs []int64
			ucMap := make(map[int64]*model.UserCoupons)
			for _, uc := range userCoupons {
				sysCouponIDs = append(sysCouponIDs, uc.CouponID)
				ucMap[uc.ID] = uc
			}
			if len(sysCouponIDs) > 0 {
				sysCoupons, _ := h.readDB.SystemCoupons.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.SystemCoupons.ID.In(sysCouponIDs...)).Find()
				scMap := make(map[int64]*model.SystemCoupons)
				for _, sc := range sysCoupons {
					scMap[sc.ID] = sc
				}
				for id, uc := range ucMap {
					if sc, ok := scMap[uc.CouponID]; ok {
						couponMap[id] = map[string]any{
							"user_coupon_id": uc.ID,
							"name":           sc.Name,
							"type":           sc.DiscountType,
							"value":          sc.DiscountValue,
						}
					}
				}
			}

			// 查询 OrderCoupons 获取实际抵扣金额
			if len(orderIDs) > 0 {
				ocs, _ := h.readDB.OrderCoupons.WithContext(ctx.RequestContext()).ReadDB().Where(
					h.readDB.OrderCoupons.OrderID.In(orderIDs...),
					h.readDB.OrderCoupons.UserCouponID.In(userCouponIDs...),
				).Find()
				for _, oc := range ocs {
					if _, ok := appliedAmountMap[oc.OrderID]; !ok {
						appliedAmountMap[oc.OrderID] = make(map[int64]int64)
					}
					appliedAmountMap[oc.OrderID][oc.UserCouponID] = oc.AppliedAmount
				}
			}
		}

		itemCardMap := make(map[int64]map[string]any)
		if len(userItemCardIDs) > 0 {
			userCards, _ := h.readDB.UserItemCards.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.UserItemCards.ID.In(userItemCardIDs...)).Find()
			var sysCardIDs []int64
			ucMap := make(map[int64]*model.UserItemCards)
			for _, uc := range userCards {
				sysCardIDs = append(sysCardIDs, uc.CardID)
				ucMap[uc.ID] = uc
			}
			if len(sysCardIDs) > 0 {
				sysCards, _ := h.readDB.SystemItemCards.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.SystemItemCards.ID.In(sysCardIDs...)).Find()
				scMap := make(map[int64]*model.SystemItemCards)
				for _, sc := range sysCards {
					scMap[sc.ID] = sc
				}
				for id, uc := range ucMap {
					if sc, ok := scMap[uc.CardID]; ok {
						itemCardMap[id] = map[string]any{
							"user_card_id": uc.ID,
							"name":         sc.Name,
							"effect_type":  sc.EffectType,
						}
					}
				}
			}
		}

		out := make([]map[string]any, 0, len(rows))
		for _, o := range rows {
			ledgers, err := h.readDB.UserPointsLedger.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.UserPointsLedger.RefTable.Eq("orders"), h.readDB.UserPointsLedger.RefID.Eq(o.OrderNo)).Find()
			if err != nil {
				h.logger.Error("ListPayOrders fetch ledgers error", zap.Error(err), zap.String("order_no", o.OrderNo))
			}
			var consumePointsSum int64
			for _, lg := range ledgers {
				if lg.Points < 0 {
					consumePointsSum += -lg.Points
				}
			}
			pa := o.PointsAmount
			if pa == 0 && consumePointsSum > 0 {
				pa = consumePointsSum / pointsRate
			}
			pu := int64(0)
			if consumePointsSum > 0 {
				pu = consumePointsSum
			} else if pa > 0 {
				pu = pa * pointsRate
			}
			var cInfo map[string]any
			if baseInfo, ok := couponMap[o.CouponID]; ok {
				cInfo = make(map[string]any)
				for k, v := range baseInfo {
					cInfo[k] = v
				}
				// 尝试使用实际抵扣金额
				if amMap, ok := appliedAmountMap[o.ID]; ok {
					if amount, ok2 := amMap[o.CouponID]; ok2 {
						cInfo["value"] = amount
					}
				}
			}

			item := map[string]any{
				"id":            o.ID,
				"order_no":      o.OrderNo,
				"user_id":       o.UserID,
				"source_type":   o.SourceType,
				"actual_amount": o.ActualAmount,
				"status":        o.Status,
				"paid_at": func() string {
					if !o.PaidAt.IsZero() {
						return o.PaidAt.Format("2006-01-02 15:04:05")
					}
					return ""
				}(),
				"created_at":     o.CreatedAt,
				"is_consumed":    o.IsConsumed,
				"remark":         o.Remark,
				"points_amount":  pa,
				"points_used":    pu,
				"total_amount":   o.TotalAmount,
				"coupon_info":    cInfo,
				"item_card_info": itemCardMap[o.ItemCardID],
			}
			out = append(out, item)
		}
		rsp.Page = req.Page
		rsp.PageSize = req.PageSize
		rsp.Total = total
		rsp.List = out
		ctx.Payload(rsp)
	}
}

type getPayOrderResponse struct {
	Order     *model.Orders             `json:"order"`
	Items     []*model.OrderItems       `json:"items"`
	Shipments []*model.ShippingRecords  `json:"shipments"`
	Ledgers   []*model.UserPointsLedger `json:"ledgers"`
	User      *model.Users              `json:"user"`
	Coupons   []*struct {
		UserCouponID  int64 `json:"user_coupon_id"`
		AppliedAmount int64 `json:"applied_amount"`
	} `json:"coupons"`
	Activity *struct {
		ActivityID   int64  `json:"activity_id"`
		ActivityName string `json:"activity_name"`
		IssueID      int64  `json:"issue_id"`
		IssueNumber  string `json:"issue_number"`
		IsWinner     int32  `json:"is_winner"`
		Level        int32  `json:"level"`
		RewardID     int64  `json:"reward_id"`
		RewardName   string `json:"reward_name"`
		ProductID    int64  `json:"product_id"`
		Count        int64  `json:"count"`
		ProductPrice int64  `json:"product_price"`
		PriceDraw    int64  `json:"price_draw"`
		PriceDrawPts int64  `json:"price_draw_points"`
	} `json:"activity"`
	Payment *struct {
		Status              int32  `json:"status"`
		ActualAmount        int64  `json:"actual_amount"`
		PaidAt              string `json:"paid_at"`
		PayPreorderID       int64  `json:"pay_preorder_id"`
		TransactionID       string `json:"transaction_id"`
		PointsAmount        int64  `json:"points_amount"`
		PointsUsed          int64  `json:"points_used"`
		CouponAppliedAmount int64  `json:"coupon_applied_amount"`
		TotalAmount         int64  `json:"total_amount"`
	} `json:"payment"`
	Refunds []*struct {
		RefundNo  string `json:"refund_no"`
		Amount    int64  `json:"amount"`
		Status    string `json:"status"`
		Channel   string `json:"channel"`
		Reason    string `json:"reason"`
		CreatedAt string `json:"created_at"`
	} `json:"refunds"`
	RefundableAmount int64 `json:"refundable_amount"`
	ComputedItems    []*struct {
		Name      string `json:"name"`
		Quantity  int64  `json:"quantity"`
		UnitPrice int64  `json:"unit_price"`
		Amount    int64  `json:"amount"`
	} `json:"computed_items"`
	RewardOrder *struct {
		OrderNo   string `json:"order_no"`
		Status    int32  `json:"status"`
		CreatedAt string `json:"created_at"`
	} `json:"reward_order"`
	RewardItems []*struct {
		Title     string `json:"title"`
		Quantity  int64  `json:"quantity"`
		UnitPrice int64  `json:"unit_price"`
		Amount    int64  `json:"amount"`
	} `json:"reward_items"`
	RewardShipments []*model.ShippingRecords   `json:"reward_shipments"`
	DrawReceipts    []*usersvc.DrawReceiptInfo `json:"draw_receipts"`
	CouponInfo      map[string]any             `json:"coupon_info"`
	ItemCardInfo    map[string]any             `json:"item_card_info"`
}

func (h *handler) GetPayOrderDetail() core.HandlerFunc {
	return func(ctx core.Context) {
		rsp := new(getPayOrderResponse)
		orderNo := ctx.Param("order_no")
		if orderNo == "" {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "缺少订单号"))
			return
		}
		order, err := h.readDB.Orders.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Orders.OrderNo.Eq(orderNo)).First()
		if err != nil || order == nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 21003, "订单不存在"))
			return
		}
		items, _ := h.readDB.OrderItems.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.OrderItems.OrderID.Eq(order.ID)).Find()
		shipments, _ := h.readDB.ShippingRecords.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.ShippingRecords.OrderID.Eq(order.ID)).Find()
		ledgers, _ := h.readDB.UserPointsLedger.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.UserPointsLedger.RefTable.Eq("orders"), h.readDB.UserPointsLedger.RefID.Eq(orderNo)).Find()
		ocRows, _ := h.readDB.OrderCoupons.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.OrderCoupons.OrderID.Eq(order.ID)).Find()
		var couponList []*struct {
			UserCouponID  int64 `json:"user_coupon_id"`
			AppliedAmount int64 `json:"applied_amount"`
		}
		var couponAppliedSum int64
		for _, r := range ocRows {
			couponList = append(couponList, &struct {
				UserCouponID  int64 `json:"user_coupon_id"`
				AppliedAmount int64 `json:"applied_amount"`
			}{UserCouponID: r.UserCouponID, AppliedAmount: r.AppliedAmount})
			couponAppliedSum += r.AppliedAmount
		}
		var consumePointsSum int64
		for _, lg := range ledgers {
			if lg.Action == "consume_order" && lg.Points < 0 {
				consumePointsSum += -lg.Points
			}
		}
		// 每分对应多少积分（默认1），用于展示积分抵扣与活动价格
		var pointsRate int64 = 1
		if cfgRate, _ := h.readDB.SystemConfigs.WithContext(ctx.RequestContext()).Where(h.readDB.SystemConfigs.ConfigKey.Eq("points_exchange_per_cent")).First(); cfgRate != nil {
			var r int64
			_, _ = fmt.Sscanf(cfgRate.ConfigValue, "%d", &r)
			if r > 0 {
				pointsRate = r
			}
		}
		var pointsAmountCents int64 = order.PointsAmount
		if pointsAmountCents == 0 && consumePointsSum > 0 {
			pointsAmountCents = consumePointsSum / pointsRate
		}
		user, _ := h.readDB.Users.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Users.ID.Eq(order.UserID)).First()
		var activityID int64
		var activityName string
		var issueID int64
		var issueNumber string
		var isWinner int32
		var level int32
		var rewardID int64
		var rewardName string
		var rewardProductID int64
		var count int64
		if order.SourceType == 2 {
			drawLog, _ := h.readDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.ActivityDrawLogs.OrderID.Eq(order.ID)).First()
			if drawLog != nil {
				isWinner = drawLog.IsWinner
				level = drawLog.Level
				rewardID = drawLog.RewardID
				issueID = drawLog.IssueID
				issue, _ := h.readDB.ActivityIssues.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.ActivityIssues.ID.Eq(issueID)).First()
				if issue != nil {
					issueNumber = issue.IssueNumber
					activityID = issue.ActivityID
					activity, _ := h.readDB.Activities.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Activities.ID.Eq(activityID)).First()
					if activity != nil {
						activityName = activity.Name
					}
				}
				if rewardID > 0 {
					reward, _ := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.ActivityRewardSettings.ID.Eq(rewardID)).First()
					if reward != nil {
						rewardProductID = reward.ProductID
					}
				}
			} else {
				remark := order.Remark
				var aid, iss, cnt int64
				p := 0
				for i := 0; i <= len(remark); i++ {
					if i == len(remark) || remark[i] == '|' {
						seg := remark[p:i]
						if len(seg) > len("lottery:activity:") && seg[:len("lottery:activity:")] == "lottery:activity:" {
							var n int64
							for j := len("lottery:activity:"); j < len(seg); j++ {
								c := seg[j]
								if c < '0' || c > '9' {
									break
								}
								n = n*10 + int64(c-'0')
							}
							aid = n
						}
						if len(seg) > 6 && seg[:6] == "issue:" {
							var n2 int64
							for j := 6; j < len(seg); j++ {
								c := seg[j]
								if c < '0' || c > '9' {
									break
								}
								n2 = n2*10 + int64(c-'0')
							}
							iss = n2
						}
						if len(seg) > 6 && seg[:6] == "count:" {
							var n3 int64
							for j := 6; j < len(seg); j++ {
								c := seg[j]
								if c < '0' || c > '9' {
									break
								}
								n3 = n3*10 + int64(c-'0')
							}
							if n3 <= 0 {
								n3 = 1
							}
							cnt = n3
						}
						p = i + 1
					}
				}
				if aid > 0 {
					activityID = aid
				}
				if iss > 0 {
					issueID = iss
				}
				count = cnt
				if issueID > 0 {
					issue, _ := h.readDB.ActivityIssues.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.ActivityIssues.ID.Eq(issueID)).First()
					if issue != nil {
						issueNumber = issue.IssueNumber
						activityID = issue.ActivityID
					}
				}
				if activityID > 0 {
					activity, _ := h.readDB.Activities.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Activities.ID.Eq(activityID)).First()
					if activity != nil {
						activityName = activity.Name
					}
				}
			}
		}
		var productPrice int64
		if rewardProductID > 0 {
			prod, _ := h.readDB.Products.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Products.ID.Eq(rewardProductID)).First()
			if prod != nil {
				productPrice = prod.Price
				// 优先使用商品名称
				if prod.Name != "" {
					rewardName = prod.Name
				}
			}
		}
		if count <= 0 {
			count = func() int64 {
				remark := order.Remark
				var n int64
				p := 0
				for i := 0; i <= len(remark); i++ {
					if i == len(remark) || remark[i] == '|' {
						seg := remark[p:i]
						if len(seg) > 6 && seg[:6] == "count:" {
							for j := 6; j < len(seg); j++ {
								c := seg[j]
								if c < '0' || c > '9' {
									break
								}
								n = n*10 + int64(c-'0')
							}
						}
						p = i + 1
					}
				}
				if n <= 0 {
					n = 1
				}
				return n
			}()
		}
		// 使用 pointsRate 展示活动价格（积分）
		rsp.Activity = &struct {
			ActivityID   int64  `json:"activity_id"`
			ActivityName string `json:"activity_name"`
			IssueID      int64  `json:"issue_id"`
			IssueNumber  string `json:"issue_number"`
			IsWinner     int32  `json:"is_winner"`
			Level        int32  `json:"level"`
			RewardID     int64  `json:"reward_id"`
			RewardName   string `json:"reward_name"`
			ProductID    int64  `json:"product_id"`
			Count        int64  `json:"count"`
			ProductPrice int64  `json:"product_price"`
			PriceDraw    int64  `json:"price_draw"`
			PriceDrawPts int64  `json:"price_draw_points"`
		}{
			ActivityID:   activityID,
			ActivityName: activityName,
			IssueID:      issueID,
			IssueNumber:  issueNumber,
			IsWinner:     isWinner,
			Level:        level,
			RewardID:     rewardID,
			RewardName:   rewardName,
			ProductID:    rewardProductID,
			Count:        count,
			ProductPrice: productPrice,
			PriceDraw: order.TotalAmount / func() int64 {
				if count > 0 {
					return count
				}
				return 1
			}(),
			PriceDrawPts: (order.TotalAmount / func() int64 {
				if count > 0 {
					return count
				}
				return 1
			}()) * pointsRate,
		}
		if user != nil {
			rsp.User = user
		}
		rsp.Payment = &struct {
			Status              int32  `json:"status"`
			ActualAmount        int64  `json:"actual_amount"`
			PaidAt              string `json:"paid_at"`
			PayPreorderID       int64  `json:"pay_preorder_id"`
			TransactionID       string `json:"transaction_id"`
			PointsAmount        int64  `json:"points_amount"`
			PointsUsed          int64  `json:"points_used"`
			CouponAppliedAmount int64  `json:"coupon_applied_amount"`
			TotalAmount         int64  `json:"total_amount"`
		}{
			Status:       order.Status,
			ActualAmount: order.ActualAmount,
			PaidAt: func() string {
				if !order.PaidAt.IsZero() {
					return order.PaidAt.Format("2006-01-02 15:04:05")
				}
				return ""
			}(),
			PayPreorderID: order.PayPreorderID,
			TransactionID: "",
			PointsAmount:  pointsAmountCents,
			PointsUsed: func() int64 {
				if consumePointsSum > 0 {
					return consumePointsSum
				}
				if pointsAmountCents > 0 {
					return pointsAmountCents * pointsRate
				}
				return 0
			}(),
			CouponAppliedAmount: couponAppliedSum,
			TotalAmount:         order.TotalAmount,
		}
		tx, _ := h.readDB.PaymentTransactions.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.PaymentTransactions.OrderNo.Eq(order.OrderNo)).Order(h.readDB.PaymentTransactions.ID.Desc()).First()
		if tx != nil {
			rsp.Payment.PaidAt = tx.SuccessTime.Format("2006-01-02 15:04:05")
			rsp.Payment.TransactionID = tx.TransactionID
		}
		var refundedSum int64
		var refunds []*struct {
			RefundNo  string `json:"refund_no"`
			Amount    int64  `json:"amount"`
			Status    string `json:"status"`
			Channel   string `json:"channel"`
			Reason    string `json:"reason"`
			CreatedAt string `json:"created_at"`
		}
		prList, _ := h.readDB.PaymentRefunds.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.PaymentRefunds.OrderNo.Eq(order.OrderNo)).Order(h.readDB.PaymentRefunds.ID.Desc()).Find()
		for _, r := range prList {
			refundedSum += r.AmountRefund
			refunds = append(refunds, &struct {
				RefundNo  string `json:"refund_no"`
				Amount    int64  `json:"amount"`
				Status    string `json:"status"`
				Channel   string `json:"channel"`
				Reason    string `json:"reason"`
				CreatedAt string `json:"created_at"`
			}{RefundNo: r.RefundNo, Amount: r.AmountRefund, Status: r.Status, Channel: r.Channel, Reason: r.Reason, CreatedAt: r.CreatedAt.Format("2006-01-02 15:04:05")})
		}
		var refundable int64 = order.ActualAmount - refundedSum
		if refundable < 0 {
			refundable = 0
		}
		rsp.Refunds = refunds
		rsp.RefundableAmount = refundable
		rsp.Order = order
		rsp.Items = items
		rsp.Coupons = couponList
		rsp.Shipments = shipments
		rsp.Ledgers = ledgers
		if order.SourceType == 2 {
			unit := int64(0)
			if count > 0 {
				unit = order.TotalAmount / count
			}
			name := rewardName
			if name == "" {
				name = "抽奖参与"
			}
			rsp.ComputedItems = []*struct {
				Name      string `json:"name"`
				Quantity  int64  `json:"quantity"`
				UnitPrice int64  `json:"unit_price"`
				Amount    int64  `json:"amount"`
			}{
				{Name: name, Quantity: count, UnitPrice: unit, Amount: order.TotalAmount},
			}
			// 合并中奖发放信息：按该订单的全部抽奖日志汇总发放订单项与物流
			logsAll, _ := h.readDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.ActivityDrawLogs.OrderID.Eq(order.ID)).Find()
			var risAll []*struct {
				Title     string `json:"title"`
				Quantity  int64  `json:"quantity"`
				UnitPrice int64  `json:"unit_price"`
				Amount    int64  `json:"amount"`
			}
			var shipsAll []*model.ShippingRecords
			for _, lg := range logsAll {
				if lg.RewardID <= 0 {
					continue
				}
				inv, _ := h.readDB.UserInventory.WithContext(ctx.RequestContext()).ReadDB().Where(
					h.readDB.UserInventory.UserID.Eq(order.UserID),
					h.readDB.UserInventory.RewardID.Eq(lg.RewardID),
				).Order(h.readDB.UserInventory.ID.Desc()).First()
				if inv == nil || inv.OrderID <= 0 {
					continue
				}
				rg, _ := h.readDB.Orders.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Orders.ID.Eq(inv.OrderID)).First()
				if rg == nil {
					continue
				}
				if rsp.RewardOrder == nil {
					rsp.RewardOrder = &struct {
						OrderNo   string `json:"order_no"`
						Status    int32  `json:"status"`
						CreatedAt string `json:"created_at"`
					}{
						OrderNo:   rg.OrderNo,
						Status:    rg.Status,
						CreatedAt: rg.CreatedAt.Format("2006-01-02 15:04:05"),
					}
				}
				ritems, _ := h.readDB.OrderItems.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.OrderItems.OrderID.Eq(rg.ID)).Find()
				for _, it := range ritems {
					risAll = append(risAll, &struct {
						Title     string `json:"title"`
						Quantity  int64  `json:"quantity"`
						UnitPrice int64  `json:"unit_price"`
						Amount    int64  `json:"amount"`
					}{
						Title:     it.Title,
						Quantity:  it.Quantity,
						UnitPrice: it.Price,
						Amount:    it.TotalAmount,
					})
				}
				rships, _ := h.readDB.ShippingRecords.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.ShippingRecords.OrderID.Eq(rg.ID)).Find()
				shipsAll = append(shipsAll, rships...)
			}
			if len(risAll) > 0 {
				rsp.RewardItems = risAll
			}
			if len(shipsAll) > 0 {
				rsp.RewardShipments = shipsAll
			}
		}

		// 填充抽奖凭据 Verify Seed Data
		if order.SourceType == 2 { // Buffer/Lottery orders
			drawLogs, _ := h.readDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.ActivityDrawLogs.OrderID.Eq(order.ID)).Find()
			var logIDs []int64
			for _, l := range drawLogs {
				logIDs = append(logIDs, l.ID)
			}
			if len(logIDs) > 0 {
				receipts, _ := h.readDB.ActivityDrawReceipts.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.ActivityDrawReceipts.DrawLogID.In(logIDs...)).Find()
				var drList []*usersvc.DrawReceiptInfo
				for i, r := range receipts {
					// Find rewardID from logs if possible, though receipt has DrawLogID
					var rid int64
					for _, l := range drawLogs {
						if l.ID == r.DrawLogID {
							rid = l.RewardID
							break
						}
					}
					drList = append(drList, &usersvc.DrawReceiptInfo{
						DrawLogID:      r.DrawLogID,
						RewardID:       rid,
						DrawIndex:      i + 1, // approximate
						AlgoVersion:    r.AlgoVersion,
						RoundID:        r.RoundID,
						DrawID:         r.DrawID,
						ClientID:       r.ClientID,
						Timestamp:      r.Timestamp,
						ServerSeedHash: r.ServerSeedHash,
						ServerSubSeed:  r.ServerSubSeed,
						ClientSeed:     r.ClientSeed,
						Nonce:          r.Nonce,
						ItemsRoot:      r.ItemsRoot,
						WeightsTotal:   r.WeightsTotal,
						SelectedIndex:  r.SelectedIndex,
						RandProof:      r.RandProof,
						Signature:      r.Signature,
						ItemsSnapshot:  r.ItemsSnapshot,
					})
				}
				rsp.DrawReceipts = drList
			}
		}

		// 补充优惠券和道具卡信息
		if order.CouponID > 0 {
			if uc, _ := h.readDB.UserCoupons.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.UserCoupons.ID.Eq(order.CouponID)).First(); uc != nil {
				if sc, _ := h.readDB.SystemCoupons.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.SystemCoupons.ID.Eq(uc.CouponID)).First(); sc != nil {
					val := sc.DiscountValue
					// 尝试查找实际抵扣金额
					// 上面已经查询了 ocRows，直接遍历查找
					for _, r := range ocRows {
						if r.UserCouponID == order.CouponID {
							val = r.AppliedAmount
							break
						}
					}

					rsp.CouponInfo = map[string]any{
						"user_coupon_id": uc.ID,
						"name":           sc.Name,
						"type":           sc.DiscountType,
						"value":          val,
					}
				}
			}
		}
		if order.ItemCardID > 0 {
			if uc, _ := h.readDB.UserItemCards.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.UserItemCards.ID.Eq(order.ItemCardID)).First(); uc != nil {
				if sc, _ := h.readDB.SystemItemCards.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.SystemItemCards.ID.Eq(uc.CardID)).First(); sc != nil {
					rsp.ItemCardInfo = map[string]any{
						"user_card_id": uc.ID,
						"name":         sc.Name,
						"effect_type":  sc.EffectType,
					}
				}
			}
		}

		ctx.Payload(rsp)
	}
}

func (h *handler) UploadMiniAppVirtualShippingForOrder() core.HandlerFunc {
	return func(ctx core.Context) {
		orderNo := ctx.Param("order_no")
		if orderNo == "" {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "缺少订单号"))
			return
		}
		order, err := h.readDB.Orders.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Orders.OrderNo.Eq(orderNo)).First()
		if err != nil || order == nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 21003, "订单不存在"))
			return
		}
		if order.Status != 2 {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 21006, "仅支持已支付订单发货"))
			return
		}
		tx, _ := h.readDB.PaymentTransactions.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.PaymentTransactions.OrderNo.Eq(order.OrderNo)).Order(h.readDB.PaymentTransactions.ID.Desc()).First()
		if tx == nil || tx.TransactionID == "" {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 21004, "未找到支付交易"))
			return
		}
		var itemDesc string
		xs, _ := h.readDB.OrderItems.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.OrderItems.OrderID.Eq(order.ID)).Find()
		if len(xs) == 0 {
			itemDesc = "订单" + order.OrderNo
		} else {
			s := ""
			for i, it := range xs {
				seg := it.Title + "*" + func(q int64) string { return fmt.Sprintf("%d", q) }(it.Quantity)
				if i == 0 {
					s = seg
				} else {
					s = s + ", " + seg
				}
			}
			if len(s) > 120 {
				s = s[:120]
			}
			itemDesc = s
		}
		pre, _ := h.readDB.PaymentPreorders.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.PaymentPreorders.OrderID.Eq(order.ID)).Order(h.readDB.PaymentPreorders.ID.Desc()).First()
		payerOpenid := ""
		if pre != nil {
			payerOpenid = pre.PayerOpenid
		}
		cfg := configs.Get()
		wxc := &wechat.WechatConfig{AppID: cfg.Wechat.AppID, AppSecret: cfg.Wechat.AppSecret}
		if err := wechat.UploadVirtualShippingWithFallback(ctx, wxc, tx.TransactionID, order.OrderNo, payerOpenid, itemDesc, time.Now()); err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 21005, err.Error()))
			return
		}
		ctx.Payload(map[string]any{"ok": true})
	}
}

type updateOrderRemarkRequest struct {
	Remark string `json:"remark"`
}

type simpleResponse struct {
	Success bool `json:"success"`
}

func (h *handler) UpdateOrderRemark() core.HandlerFunc {
	return func(ctx core.Context) {
		req := new(updateOrderRemarkRequest)
		rsp := new(simpleResponse)
		if err := ctx.ShouldBindJSON(req); err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
			return
		}
		orderNo := ctx.Param("order_no")
		if orderNo == "" {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "缺少订单号"))
			return
		}
		order, err := h.readDB.Orders.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Orders.OrderNo.Eq(orderNo)).First()
		if err != nil || order == nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 21004, "订单不存在"))
			return
		}
		_, err = h.writeDB.Orders.WithContext(ctx.RequestContext()).Where(h.readDB.Orders.ID.Eq(order.ID)).Updates(map[string]any{
			h.writeDB.Orders.Remark.ColumnName().String():    req.Remark,
			h.writeDB.Orders.UpdatedAt.ColumnName().String(): time.Now(),
		})
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 21005, err.Error()))
			return
		}
		rsp.Success = true
		ctx.Payload(rsp)
	}
}

func (h *handler) CancelOrder() core.HandlerFunc {
	return func(ctx core.Context) {
		orderNo := ctx.Param("order_no")
		if orderNo == "" {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "缺少订单号"))
			return
		}
		order, err := h.readDB.Orders.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Orders.OrderNo.Eq(orderNo)).First()
		if err != nil || order == nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 21006, "订单不存在"))
			return
		}
		_, err = h.writeDB.Orders.WithContext(ctx.RequestContext()).Where(h.readDB.Orders.ID.Eq(order.ID), h.readDB.Orders.Status.Eq(1)).Updates(map[string]any{
			h.readDB.Orders.Status.ColumnName().String():      3,
			h.readDB.Orders.CancelledAt.ColumnName().String(): time.Now(),
		})
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 21007, err.Error()))
			return
		}
		ctx.Payload(&simpleResponse{Success: true})
	}
}

func (h *handler) ConsumeOrder() core.HandlerFunc {
	return func(ctx core.Context) {
		orderNo := ctx.Param("order_no")
		if orderNo == "" {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "缺少订单号"))
			return
		}
		order, err := h.readDB.Orders.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Orders.OrderNo.Eq(orderNo)).First()
		if err != nil || order == nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 21008, "订单不存在"))
			return
		}
		_, err = h.writeDB.Orders.WithContext(ctx.RequestContext()).Where(h.readDB.Orders.ID.Eq(order.ID), h.readDB.Orders.Status.Eq(2)).Updates(map[string]any{
			h.readDB.Orders.IsConsumed.ColumnName().String(): 1,
		})
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 21009, err.Error()))
			return
		}
		ctx.Payload(&simpleResponse{Success: true})
	}
}
