package admin

import (
	"net/http"
	"strconv"
	"time"

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

type listUsersRequest struct {
	Page       int    `form:"page"`
	PageSize   int    `form:"page_size"`
	Nickname   string `form:"nickname"`
	InviteCode string `form:"inviteCode"`
	StartDate  string `form:"startDate"`
	EndDate    string `form:"endDate"`
	ID         *int64 `form:"id"`
}
type listUsersResponse struct {
	Page     int             `json:"page"`
	PageSize int             `json:"page_size"`
	Total    int64           `json:"total"`
	List     []adminUserItem `json:"list"`
}

// ListAppUsers 管理端用户列表
// @Summary 管理端用户列表
// @Description 查看APP端用户分页列表
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param page query int true "页码" default(1)
// @Param page_size query int true "每页数量，最多100" default(20)
// @Param nickname query string false "用户昵称"
// @Param inviteCode query string false "邀请码"
// @Param startDate query string false "开始日期(YYYY-MM-DD)"
// @Param endDate query string false "结束日期(YYYY-MM-DD)"
// @Success 200 {object} listUsersResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/users [get]
// @Security LoginVerifyToken
func (h *handler) ListAppUsers() core.HandlerFunc {
	return func(ctx core.Context) {
		req := new(listUsersRequest)
		rsp := new(listUsersResponse)
		if err := ctx.ShouldBindForm(req); err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
			return
		}
		if req.Page <= 0 {
			req.Page = 1
		}
		if req.PageSize <= 0 {
			req.PageSize = 20
		}
		if req.PageSize > 100 {
			req.PageSize = 100
		}
		u := h.readDB.Users
		c := h.readDB.Channels

		q := h.readDB.Users.WithContext(ctx.RequestContext()).ReadDB().
			LeftJoin(c, c.ID.EqCol(u.ChannelID)).
			Select(
				u.ALL,
				c.Name.As("channel_name"),
				c.Code.As("channel_code"),
			)

		// 应用搜索条件
		if req.ID != nil {
			q = q.Where(h.readDB.Users.ID.Eq(*req.ID))
		}
		if req.Nickname != "" {
			q = q.Where(h.readDB.Users.Nickname.Like("%" + req.Nickname + "%"))
		}
		if req.InviteCode != "" {
			q = q.Where(h.readDB.Users.InviteCode.Eq(req.InviteCode))
		}
		if req.StartDate != "" {
			if startTime, err := time.Parse("2006-01-02", req.StartDate); err == nil {
				q = q.Where(h.readDB.Users.CreatedAt.Gte(startTime))
			}
		}
		if req.EndDate != "" {
			if endTime, err := time.Parse("2006-01-02", req.EndDate); err == nil {
				// 设置结束时间为当天的23:59:59
				endTime = endTime.Add(24 * time.Hour).Add(-time.Second)
				q = q.Where(h.readDB.Users.CreatedAt.Lte(endTime))
			}
		}

		total, err := q.Count()
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 20101, err.Error()))
			return
		}
		type result struct {
			model.Users
			ChannelName string
			ChannelCode string
		}
		var rows []result
		if err := q.Order(h.readDB.Users.ID.Desc()).Offset((req.Page - 1) * req.PageSize).Limit(req.PageSize).Scan(&rows); err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 20102, err.Error()))
			return
		}

		// 获取用户ID列表以批量查询资产
		userIDs := make([]int64, len(rows))
		for i, v := range rows {
			userIDs[i] = v.ID
		}

		// 批量查询优惠券数量（未使用的）
		couponCounts := make(map[int64]int64)
		if len(userIDs) > 0 {
			type countResult struct {
				UserID int64
				Count  int64
			}
			var counts []countResult
			h.readDB.UserCoupons.WithContext(ctx.RequestContext()).ReadDB().
				Select(h.readDB.UserCoupons.UserID, h.readDB.UserCoupons.ID.Count().As("count")).
				Where(h.readDB.UserCoupons.UserID.In(userIDs...)).
				Where(h.readDB.UserCoupons.Status.Eq(1)). // 1=未使用
				Group(h.readDB.UserCoupons.UserID).
				Scan(&counts)
			for _, c := range counts {
				couponCounts[c.UserID] = c.Count
			}
		}

		// 批量查询积分余额
		pointBalances := make(map[int64]int64)
		if len(userIDs) > 0 {
			type balResult struct {
				UserID int64
				Points int64
			}
			var bRes []balResult
			h.readDB.UserPoints.WithContext(ctx.RequestContext()).ReadDB().
				Select(h.readDB.UserPoints.UserID, h.readDB.UserPoints.Points.Sum().As("points")).
				Where(h.readDB.UserPoints.UserID.In(userIDs...)).
				Group(h.readDB.UserPoints.UserID).
				Scan(&bRes)
			for _, b := range bRes {
				pointBalances[b.UserID] = b.Points
			}
		}

		// 批量查询优惠券数量（未使用的）
		cardCounts := make(map[int64]int64)
		if len(userIDs) > 0 {
			type countResult struct {
				UserID int64
				Count  int64
			}
			var counts []countResult
			h.readDB.UserItemCards.WithContext(ctx.RequestContext()).ReadDB().
				Select(h.readDB.UserItemCards.UserID, h.readDB.UserItemCards.ID.Count().As("count")).
				Where(h.readDB.UserItemCards.UserID.In(userIDs...)).
				Where(h.readDB.UserItemCards.Status.Eq(1)). // 1=未使用
				Group(h.readDB.UserItemCards.UserID).
				Scan(&counts)
			for _, c := range counts {
				cardCounts[c.UserID] = c.Count
			}
		}

		// 批量查询消费统计
		todayConsume := make(map[int64]int64)
		sevenDayConsume := make(map[int64]int64)
		thirtyDayConsume := make(map[int64]int64)
		totalConsume := make(map[int64]int64)
		if len(userIDs) > 0 {
			now := time.Now()
			todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
			sevenDayStart := todayStart.AddDate(0, 0, -6)
			thirtyDayStart := todayStart.AddDate(0, 0, -29)

			type consumeResult struct {
				UserID int64
				Amount int64
			}

			// 当日消费
			var todayRes []consumeResult
			h.readDB.Orders.WithContext(ctx.RequestContext()).ReadDB().
				Select(h.readDB.Orders.UserID, h.readDB.Orders.ActualAmount.Sum().As("amount")).
				Where(h.readDB.Orders.UserID.In(userIDs...)).
				Where(h.readDB.Orders.Status.Eq(2)).
				Where(h.readDB.Orders.CreatedAt.Gte(todayStart)).
				Group(h.readDB.Orders.UserID).
				Scan(&todayRes)
			for _, r := range todayRes {
				todayConsume[r.UserID] = r.Amount
			}

			// 近7天消费
			var sevenRes []consumeResult
			h.readDB.Orders.WithContext(ctx.RequestContext()).ReadDB().
				Select(h.readDB.Orders.UserID, h.readDB.Orders.ActualAmount.Sum().As("amount")).
				Where(h.readDB.Orders.UserID.In(userIDs...)).
				Where(h.readDB.Orders.Status.Eq(2)).
				Where(h.readDB.Orders.CreatedAt.Gte(sevenDayStart)).
				Group(h.readDB.Orders.UserID).
				Scan(&sevenRes)
			for _, r := range sevenRes {
				sevenDayConsume[r.UserID] = r.Amount
			}

			// 近30天消费
			var thirtyRes []consumeResult
			h.readDB.Orders.WithContext(ctx.RequestContext()).ReadDB().
				Select(h.readDB.Orders.UserID, h.readDB.Orders.ActualAmount.Sum().As("amount")).
				Where(h.readDB.Orders.UserID.In(userIDs...)).
				Where(h.readDB.Orders.Status.Eq(2)).
				Where(h.readDB.Orders.CreatedAt.Gte(thirtyDayStart)).
				Group(h.readDB.Orders.UserID).
				Scan(&thirtyRes)
			for _, r := range thirtyRes {
				thirtyDayConsume[r.UserID] = r.Amount
			}

			// 累计消费
			var totalRes []consumeResult
			h.readDB.Orders.WithContext(ctx.RequestContext()).ReadDB().
				Select(h.readDB.Orders.UserID, h.readDB.Orders.ActualAmount.Sum().As("amount")).
				Where(h.readDB.Orders.UserID.In(userIDs...)).
				Where(h.readDB.Orders.Status.Eq(2)).
				Group(h.readDB.Orders.UserID).
				Scan(&totalRes)
			for _, r := range totalRes {
				totalConsume[r.UserID] = r.Amount
			}
		}

		// 批量查询邀请人数
		inviteCounts := make(map[int64]int64)
		if len(userIDs) > 0 {
			type countResult struct {
				InviterID int64
				Count     int64
			}
			var counts []countResult
			h.readDB.Users.WithContext(ctx.RequestContext()).ReadDB().
				Select(h.readDB.Users.InviterID, h.readDB.Users.ID.Count().As("count")).
				Where(h.readDB.Users.InviterID.In(userIDs...)).
				Group(h.readDB.Users.InviterID).
				Scan(&counts)
			for _, c := range counts {
				inviteCounts[c.InviterID] = c.Count
			}
		}

		// 批量查询次数卡数量
		gamePassCounts := make(map[int64]int64)
		if len(userIDs) > 0 {
			type countResult struct {
				UserID int64
				Count  int64
			}
			var counts []countResult
			now := time.Now()
			h.readDB.UserGamePasses.WithContext(ctx.RequestContext()).ReadDB().
				Select(h.readDB.UserGamePasses.UserID, h.readDB.UserGamePasses.Remaining.Sum().As("count")).
				Where(h.readDB.UserGamePasses.UserID.In(userIDs...)).
				Where(h.readDB.UserGamePasses.Remaining.Gt(0)).
				Where(h.readDB.UserGamePasses.Where(h.readDB.UserGamePasses.ExpiredAt.Gt(now)).Or(h.readDB.UserGamePasses.ExpiredAt.IsNull())).
				Group(h.readDB.UserGamePasses.UserID).
				Scan(&counts)
			for _, c := range counts {
				gamePassCounts[c.UserID] = c.Count
			}
		}

		// 批量查询游戏资格数量
		gameTicketCounts := make(map[int64]int64)
		if len(userIDs) > 0 {
			type countResult struct {
				UserID int64
				Count  int64
			}
			var counts []countResult
			h.readDB.UserGameTickets.WithContext(ctx.RequestContext()).ReadDB().
				Select(h.readDB.UserGameTickets.UserID, h.readDB.UserGameTickets.Available.Sum().As("count")).
				Where(h.readDB.UserGameTickets.UserID.In(userIDs...)).
				Group(h.readDB.UserGameTickets.UserID).
				Scan(&counts)
			for _, c := range counts {
				gameTicketCounts[c.UserID] = c.Count
			}
		}

		// 批量查询所有用户的邀请人昵称
		inviterNicknames := make(map[int64]string)
		inviterIDs := make([]int64, 0)
		for _, v := range rows {
			if v.InviterID > 0 {
				inviterIDs = append(inviterIDs, v.InviterID)
			}
		}
		if len(inviterIDs) > 0 {
			inviters, _ := h.readDB.Users.WithContext(ctx.RequestContext()).Where(h.readDB.Users.ID.In(inviterIDs...)).Find()
			for _, inv := range inviters {
				inviterNicknames[inv.ID] = inv.Nickname
			}
		}

		rsp.Page = req.Page
		rsp.PageSize = req.PageSize
		rsp.Total = total
		rsp.List = make([]adminUserItem, len(rows))
		for i, v := range rows {
			rsp.List[i] = adminUserItem{
				ID:               v.ID,
				Nickname:         v.Nickname,
				Avatar:           v.Avatar,
				InviteCode:       v.InviteCode,
				InviterID:        v.InviterID,
				InviterNickname:  inviterNicknames[v.InviterID],
				CreatedAt:        v.CreatedAt.Format("2006-01-02 15:04:05"),
				DouyinID:         v.DouyinID,
				ChannelName:      v.ChannelName,
				ChannelCode:      v.ChannelCode,
				PointsBalance:    pointBalances[v.ID],
				CouponsCount:     couponCounts[v.ID],
				ItemCardsCount:   cardCounts[v.ID],
				TodayConsume:     todayConsume[v.ID],
				SevenDayConsume:  sevenDayConsume[v.ID],
				ThirtyDayConsume: thirtyDayConsume[v.ID],
				TotalConsume:     totalConsume[v.ID],
				InviteCount:      inviteCounts[v.ID],
				GamePassCount:    gamePassCounts[v.ID],
				GameTicketCount:  gameTicketCounts[v.ID],
			}
		}
		ctx.Payload(rsp)
	}
}

type listInvitesRequest struct {
	Page     int `form:"page"`
	PageSize int `form:"page_size"`
}
type listInvitesResponse struct {
	Page     int             `json:"page"`
	PageSize int             `json:"page_size"`
	Total    int64           `json:"total"`
	List     []adminUserItem `json:"list"`
}

// ListUserInvites 查看用户邀请列表
// @Summary 查看用户邀请列表
// @Description 查看指定用户邀请的用户列表
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Param page query int true "页码" default(1)
// @Param page_size query int true "每页数量，最多100" default(20)
// @Success 200 {object} listInvitesResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/users/{user_id}/invites [get]
// @Security LoginVerifyToken
func (h *handler) ListUserInvites() core.HandlerFunc {
	return func(ctx core.Context) {
		req := new(listInvitesRequest)
		rsp := new(listInvitesResponse)
		if err := ctx.ShouldBindForm(req); err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
			return
		}
		userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
			return
		}
		rows, total, err := h.userSvc.ListInvites(ctx.RequestContext(), userID, req.Page, req.PageSize)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 20103, err.Error()))
			return
		}
		rsp.Page = req.Page
		rsp.PageSize = req.PageSize
		rsp.Total = total
		rsp.List = make([]adminUserItem, len(rows))
		for i, v := range rows {
			rsp.List[i] = adminUserItem{ID: v.ID, Nickname: v.Nickname, Avatar: v.Avatar, InviteCode: v.InviteCode, InviterID: v.InviterID, CreatedAt: v.CreatedAt.Format("2006-01-02 15:04:05")}
		}
		ctx.Payload(rsp)
	}
}

type listOrdersRequest struct {
	Page     int `form:"page"`
	PageSize int `form:"page_size"`
}
type listOrdersResponse struct {
	Page     int                    `json:"page"`
	PageSize int                    `json:"page_size"`
	Total    int64                  `json:"total"`
	List     []*user.OrderWithItems `json:"list"`
}

type listInventoryRequest struct {
	Page     int `form:"page"`
	PageSize int `form:"page_size"`
}
type listInventoryResponse struct {
	Page     int                          `json:"page"`
	PageSize int                          `json:"page_size"`
	Total    int64                        `json:"total"`
	List     []*user.InventoryWithProduct `json:"list"`
}

// ListUserOrders 查看用户订单列表
// @Summary 查看用户订单列表
// @Description 查看指定用户的订单记录
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Param page query int true "页码" default(1)
// @Param page_size query int true "每页数量，最多100" default(20)
// @Success 200 {object} listOrdersResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/users/{user_id}/orders [get]
// @Security LoginVerifyToken
func (h *handler) ListUserOrders() core.HandlerFunc {
	return func(ctx core.Context) {
		req := new(listOrdersRequest)
		rsp := new(listOrdersResponse)
		if err := ctx.ShouldBindForm(req); err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
			return
		}
		userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
			return
		}
		items, total, err := h.userSvc.ListOrdersWithItems(ctx.RequestContext(), userID, 0, nil, req.Page, req.PageSize)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 20104, err.Error()))
			return
		}
		rsp.Page = req.Page
		rsp.PageSize = req.PageSize
		rsp.Total = total
		rsp.List = items
		ctx.Payload(rsp)
	}
}

// 查看用户资产列表
// @Summary 查看用户资产列表
// @Description 查看指定用户的资产记录
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Param page query int true "页码" default(1)
// @Param page_size query int true "每页数量，最多100" default(20)
// @Success 200 {object} listInventoryResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/users/{user_id}/inventory [get]
// @Security LoginVerifyToken
func (h *handler) ListUserInventory() core.HandlerFunc {
	return func(ctx core.Context) {
		req := new(listInventoryRequest)
		rsp := new(listInventoryResponse)
		if err := ctx.ShouldBindForm(req); err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
			return
		}
		userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
			return
		}
		rows, total, err := h.userSvc.ListInventoryWithProduct(ctx.RequestContext(), userID, req.Page, req.PageSize)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 20105, err.Error()))
			return
		}
		rsp.Page = req.Page
		rsp.PageSize = req.PageSize
		rsp.Total = total
		rsp.List = rows
		ctx.Payload(rsp)
	}
}

type listUserItemCardsRequest struct {
	Page     int `form:"page"`
	PageSize int `form:"page_size"`
}

type listUserItemCardsResponse struct {
	Page     int                          `json:"page"`
	PageSize int                          `json:"page_size"`
	Total    int64                        `json:"total"`
	List     []*user.ItemCardWithTemplate `json:"list"`
}

// ListUserItemCards 查看用户道具卡列表
// @Summary 查看用户道具卡列表
// @Description 查看指定用户的道具卡持有记录
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Param page query int true "页码" default(1)
// @Param page_size query int true "每页数量，最多100" default(20)
// @Success 200 {object} listUserItemCardsResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/users/{user_id}/item_cards [get]
// @Security LoginVerifyToken
func (h *handler) ListUserItemCards() core.HandlerFunc {
	return func(ctx core.Context) {
		req := new(listUserItemCardsRequest)
		rsp := new(listUserItemCardsResponse)
		if err := ctx.ShouldBindForm(req); err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
			return
		}
		userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
			return
		}
		items, total, err := h.userSvc.ListUserItemCardsWithTemplate(ctx.RequestContext(), userID, req.Page, req.PageSize)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 20106, err.Error()))
			return
		}
		rsp.Page = req.Page
		rsp.PageSize = req.PageSize
		rsp.Total = total
		rsp.List = items
		ctx.Payload(rsp)
	}
}

type listCouponsRequest struct {
	Page     int `form:"page"`
	PageSize int `form:"page_size"`
}
type adminUserCouponItem struct {
	ID            int64  `json:"id"`
	CouponID      int64  `json:"coupon_id"`
	Status        int32  `json:"status"`
	UsedOrderID   int64  `json:"used_order_id"`
	UsedAt        string `json:"used_at"`
	ValidStart    string `json:"valid_start"`
	ValidEnd      string `json:"valid_end"`
	Name          string `json:"name"`
	ScopeType     int32  `json:"scope_type"`
	DiscountType  int32  `json:"discount_type"`
	DiscountValue int64  `json:"discount_value"`
	MinSpend      int64  `json:"min_spend"`
	BalanceAmount int64  `json:"balance_amount"`
}

type listCouponsResponse struct {
	Page     int                   `json:"page"`
	PageSize int                   `json:"page_size"`
	Total    int64                 `json:"total"`
	List     []adminUserCouponItem `json:"list"`
}

// ListUserCoupons 查看用户优惠券列表
// @Summary 查看用户优惠券列表
// @Description 查看指定用户持有的优惠券列表
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Param page query int true "页码" default(1)
// @Param page_size query int true "每页数量，最多100" default(20)
// @Success 200 {object} listCouponsResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/users/{user_id}/coupons [get]
// @Security LoginVerifyToken
func (h *handler) ListUserCoupons() core.HandlerFunc {
	return func(ctx core.Context) {
		req := new(listCouponsRequest)
		rsp := new(listCouponsResponse)
		if err := ctx.ShouldBindForm(req); err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
			return
		}
		userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
			return
		}
		// 统计总数
		base := h.readDB.UserCoupons.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.UserCoupons.UserID.Eq(userID))
		total, err := base.Count()
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 20105, err.Error()))
			return
		}
		// 联表查询 system_coupons 获取优惠券模板信息
		type row struct {
			ID            int64
			CouponID      int64
			Status        int32
			UsedOrderID   int64
			UsedAt        *string
			ValidStart    *string
			ValidEnd      *string
			Name          string
			ScopeType     int32
			DiscountType  int32
			DiscountValue int64
			MinSpend      int64
			BalanceAmount int64
		}
		q := h.readDB.UserCoupons.WithContext(ctx.RequestContext()).ReadDB().
			LeftJoin(h.readDB.SystemCoupons, h.readDB.SystemCoupons.ID.EqCol(h.readDB.UserCoupons.CouponID)).
			Select(
				h.readDB.UserCoupons.ID, h.readDB.UserCoupons.CouponID, h.readDB.UserCoupons.Status,
				h.readDB.UserCoupons.UsedOrderID, h.readDB.UserCoupons.UsedAt, h.readDB.UserCoupons.ValidStart, h.readDB.UserCoupons.ValidEnd,
				h.readDB.SystemCoupons.Name, h.readDB.SystemCoupons.ScopeType, h.readDB.SystemCoupons.DiscountType,
				h.readDB.SystemCoupons.DiscountValue, h.readDB.SystemCoupons.MinSpend,
				h.readDB.UserCoupons.BalanceAmount,
			).
			Where(h.readDB.UserCoupons.UserID.Eq(userID)).
			Order(h.readDB.UserCoupons.ID.Desc()).
			Limit(req.PageSize).Offset((req.Page - 1) * req.PageSize)

		var rows []row
		if err := q.Scan(&rows); err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 20105, err.Error()))
			return
		}

		rsp.Page = req.Page
		rsp.PageSize = req.PageSize
		rsp.Total = total
		rsp.List = make([]adminUserCouponItem, len(rows))
		for i, v := range rows {
			rsp.List[i] = adminUserCouponItem{
				ID:            v.ID,
				CouponID:      v.CouponID,
				Status:        v.Status,
				UsedOrderID:   v.UsedOrderID,
				UsedAt:        nullableToString(v.UsedAt),
				ValidStart:    nullableToString(v.ValidStart),
				ValidEnd:      nullableToString(v.ValidEnd),
				Name:          v.Name,
				ScopeType:     v.ScopeType,
				DiscountType:  v.DiscountType,
				DiscountValue: v.DiscountValue,
				MinSpend:      v.MinSpend,
				BalanceAmount: v.BalanceAmount,
			}
		}
		ctx.Payload(rsp)
	}
}

func nullableToString(s *string) string {
	if s == nil {
		return ""
	}
	return *s
}

type listPointsRequest struct {
	Page     int `form:"page"`
	PageSize int `form:"page_size"`
}
type listPointsResponse struct {
	Page     int                       `json:"page"`
	PageSize int                       `json:"page_size"`
	Total    int64                     `json:"total"`
	List     []*model.UserPointsLedger `json:"list"`
}

// ListUserPoints 查看用户积分记录
// @Summary 查看用户积分记录
// @Description 查看指定用户的积分流水记录
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Param page query int true "页码" default(1)
// @Param page_size query int true "每页数量，最多100" default(20)
// @Success 200 {object} listPointsResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/users/{user_id}/points [get]
// @Security LoginVerifyToken
func (h *handler) ListUserPoints() core.HandlerFunc {
	return func(ctx core.Context) {
		req := new(listPointsRequest)
		rsp := new(listPointsResponse)
		if err := ctx.ShouldBindForm(req); err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
			return
		}
		userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
			return
		}
		items, total, err := h.userSvc.ListPointsLedger(ctx.RequestContext(), userID, req.Page, req.PageSize)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 20106, err.Error()))
			return
		}
		rsp.Page = req.Page
		rsp.PageSize = req.PageSize
		rsp.Total = total
		rsp.List = items
		ctx.Payload(rsp)
	}
}

type pointsBalanceResponse struct {
	Balance int64 `json:"balance"`
}

type adminUserItem struct {
	ID               int64  `json:"id"`
	Nickname         string `json:"nickname"`
	Avatar           string `json:"avatar"`
	InviteCode       string `json:"invite_code"`
	InviterID        int64  `json:"inviter_id"`
	InviterNickname  string `json:"inviter_nickname"` // 邀请人昵称
	CreatedAt        string `json:"created_at"`
	DouyinID         string `json:"douyin_id"`
	ChannelName      string `json:"channel_name"`
	ChannelCode      string `json:"channel_code"`
	PointsBalance    int64  `json:"points_balance"`
	CouponsCount     int64  `json:"coupons_count"`
	ItemCardsCount   int64  `json:"item_cards_count"`
	TodayConsume     int64  `json:"today_consume"`
	SevenDayConsume  int64  `json:"seven_day_consume"`
	ThirtyDayConsume int64  `json:"thirty_day_consume"` // 近30天消费
	TotalConsume     int64  `json:"total_consume"`      // 累计消费
	InviteCount      int64  `json:"invite_count"`       // 邀请人数
	GamePassCount    int64  `json:"game_pass_count"`    // 次数卡数量
	GameTicketCount  int64  `json:"game_ticket_count"`  // 游戏资格数量
}

// ListAppUsers 管理端用户列表GetUserPointsBalance 查看用户积分余额
// @Summary 查看用户积分余额
// @Description 查看指定用户当前积分余额（过滤过期）
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Success 200 {object} pointsBalanceResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/users/{user_id}/points/balance [get]
// @Security LoginVerifyToken
func (h *handler) GetUserPointsBalance() core.HandlerFunc {
	return func(ctx core.Context) {
		rsp := new(pointsBalanceResponse)
		userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
			return
		}
		total, err := h.userSvc.GetPointsBalance(ctx.RequestContext(), userID)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 20107, err.Error()))
			return
		}
		rsp.Balance = total
		ctx.Payload(rsp)
	}
}

type addPointsRequest struct {
	Points    int64  `json:"points"` // 正数=增加，负数=扣减
	Kind      string `json:"kind"`
	Remark    string `json:"remark"`
	ValidDays *int   `json:"valid_days"`
}
type addPointsResponse struct {
	Success bool `json:"success"`
}

// AddUserPoints 给用户增加或扣减积分
// @Summary 给用户增加或扣减积分
// @Description 管理端为指定用户发放或扣减积分，正数为增加，负数为扣减
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Param RequestBody body addPointsRequest true "请求参数"
// @Success 200 {object} addPointsResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/users/{user_id}/points/add [post]
// @Security LoginVerifyToken
func (h *handler) AddUserPoints() core.HandlerFunc {
	return func(ctx core.Context) {
		req := new(addPointsRequest)
		rsp := new(addPointsResponse)
		if err := ctx.ShouldBindJSON(req); err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
			return
		}
		if req.Points == 0 {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "积分变动值不能为0"))
			return
		}
		userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
			return
		}

		// 如果是扣减积分，先检查余额
		if req.Points < 0 {
			balance, _ := h.userSvc.GetPointsBalance(ctx.RequestContext(), userID)
			if balance+req.Points < 0 {
				ctx.AbortWithError(core.Error(http.StatusBadRequest, 20108, "积分余额不足，无法扣减"))
				return
			}
		}

		var validStart *time.Time
		var validEnd *time.Time
		now := time.Now()
		// 只有增加积分时才设置有效期
		if req.Points > 0 {
			validStart = &now
			if req.ValidDays != nil && *req.ValidDays > 0 {
				ve := now.Add(time.Duration(*req.ValidDays) * 24 * time.Hour)
				validEnd = &ve
			}
		}
		if err := h.userSvc.AddPoints(ctx.RequestContext(), userID, req.Points, req.Kind, req.Remark, validStart, validEnd); err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 20108, err.Error()))
			return
		}
		rsp.Success = true
		ctx.Payload(rsp)
	}
}

type addCouponRequest struct {
	CouponID int64 `json:"coupon_id" binding:"required"`
}
type addCouponResponse struct {
	Success bool `json:"success"`
}

// AddUserCoupon 给用户添加优惠券
// @Summary 给用户添加优惠券
// @Description 管理端为指定用户发放优惠券
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Param RequestBody body addCouponRequest true "请求参数"
// @Success 200 {object} addCouponResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/users/{user_id}/coupons/add [post]
// @Security LoginVerifyToken
func (h *handler) AddUserCoupon() core.HandlerFunc {
	return func(ctx core.Context) {
		req := new(addCouponRequest)
		rsp := new(addCouponResponse)
		if err := ctx.ShouldBindJSON(req); err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
			return
		}
		userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
			return
		}
		if ctx.SessionUserInfo().IsSuper != 1 {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.CreateAdminError, "禁止操作"))
			return
		}
		if err := h.userSvc.AddCoupon(ctx.RequestContext(), userID, req.CouponID); err != nil {
			msg := err.Error()
			if msg == "unsupported data" {
				msg = "发券失败：模板配额已满"
			}
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 20109, msg))
			return
		}
		rsp.Success = true
		ctx.Payload(rsp)
	}
}

type voidUserCouponRequest struct {
}

func (h *handler) VoidUserCoupon() core.HandlerFunc {
	return func(ctx core.Context) {
		req := new(voidUserCouponRequest)
		if err := ctx.ShouldBindJSON(req); err != nil && err.Error() != "EOF" {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
			return
		}
		userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
			return
		}
		ucid, err := strconv.ParseInt(ctx.Param("user_coupon_id"), 10, 64)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递持券ID"))
			return
		}
		if ctx.SessionUserInfo().IsSuper != 1 {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.CreateAdminError, "禁止操作"))
			return
		}
		adminID := int64(ctx.SessionUserInfo().Id)
		if err := h.userSvc.VoidUserCoupon(ctx.RequestContext(), adminID, userID, ucid); err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 20109, err.Error()))
			return
		}
		ctx.Payload(simpleMessageResponse{Message: "操作成功"})
	}
}

type voidUserItemCardRequest struct {
}

func (h *handler) VoidUserItemCard() core.HandlerFunc {
	return func(ctx core.Context) {
		req := new(voidUserItemCardRequest)
		if err := ctx.ShouldBindJSON(req); err != nil && err.Error() != "EOF" {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
			return
		}
		userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
			return
		}
		icid, err := strconv.ParseInt(ctx.Param("user_item_card_id"), 10, 64)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递道具卡持有ID"))
			return
		}
		if ctx.SessionUserInfo().IsSuper != 1 {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.CreateAdminError, "禁止操作"))
			return
		}
		adminID := int64(ctx.SessionUserInfo().Id)
		if err := h.userSvc.VoidUserItemCard(ctx.RequestContext(), adminID, userID, icid); err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 20109, err.Error()))
			return
		}
		ctx.Payload(simpleMessageResponse{Message: "操作成功"})
	}
}

type voidUserInventoryRequest struct {
}

func (h *handler) VoidUserInventory() core.HandlerFunc {
	return func(ctx core.Context) {
		req := new(voidUserInventoryRequest)
		if err := ctx.ShouldBindJSON(req); err != nil && err.Error() != "EOF" {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
			return
		}
		userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
			return
		}
		invID, err := strconv.ParseInt(ctx.Param("inventory_id"), 10, 64)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递资产ID"))
			return
		}
		if ctx.SessionUserInfo().IsSuper != 1 {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.CreateAdminError, "禁止操作"))
			return
		}
		adminID := int64(ctx.SessionUserInfo().Id)
		if err := h.userSvc.VoidUserInventory(ctx.RequestContext(), adminID, userID, invID); err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 20109, err.Error()))
			return
		}
		ctx.Payload(simpleMessageResponse{Message: "操作成功"})
	}
}

type adminUserTitleItem struct {
	ID          int64  `json:"id"`
	TitleID     int64  `json:"title_id"`
	Name        string `json:"name"`
	Description string `json:"description"`
	ObtainedAt  string `json:"obtained_at"`
	ExpiresAt   string `json:"expires_at"`
	Status      int32  `json:"status"`
}

type listUserTitlesResponse struct {
	List []adminUserTitleItem `json:"list"`
}

func (h *handler) ListUserTitles() core.HandlerFunc {
	return func(ctx core.Context) {
		rsp := new(listUserTitlesResponse)
		userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
			return
		}
		type row struct {
			ID          int64
			TitleID     int64
			Active      int32
			ObtainedAt  *string
			ExpiresAt   *string
			Name        string
			Description string
		}
		q := h.readDB.UserTitles.WithContext(ctx.RequestContext()).ReadDB().
			LeftJoin(h.readDB.SystemTitles, h.readDB.SystemTitles.ID.EqCol(h.readDB.UserTitles.TitleID)).
			Select(
				h.readDB.UserTitles.ID, h.readDB.UserTitles.TitleID, h.readDB.UserTitles.Active,
				h.readDB.UserTitles.ObtainedAt, h.readDB.UserTitles.ExpiresAt,
				h.readDB.SystemTitles.Name, h.readDB.SystemTitles.Description,
			).
			Where(h.readDB.UserTitles.UserID.Eq(userID)).
			Order(h.readDB.UserTitles.ID.Desc())

		var rows []row
		if err := q.Scan(&rows); err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 20110, err.Error()))
			return
		}
		rsp.List = make([]adminUserTitleItem, len(rows))
		for i, v := range rows {
			rsp.List[i] = adminUserTitleItem{
				ID:          v.ID,
				TitleID:     v.TitleID,
				Name:        v.Name,
				Description: v.Description,
				ObtainedAt:  nullableToString(v.ObtainedAt),
				ExpiresAt:   nullableToString(v.ExpiresAt),
				Status:      v.Active,
			}
		}
		ctx.Payload(rsp)
	}
}

type listUserCouponUsageRequest struct {
	Page     int `form:"page"`
	PageSize int `form:"page_size"`
}

type adminUserCouponUsageItem struct {
	ID           int64  `json:"id"`
	UserID       int64  `json:"user_id"`
	UserCouponID int64  `json:"user_coupon_id"`
	ChangeAmount int64  `json:"change_amount"`
	BalanceAfter int64  `json:"balance_after"`
	OrderID      int64  `json:"order_id"`
	Action       string `json:"action"`
	CreatedAt    string `json:"created_at"`
}

type listUserCouponUsageResponse struct {
	Page     int                        `json:"page"`
	PageSize int                        `json:"page_size"`
	Total    int64                      `json:"total"`
	List     []adminUserCouponUsageItem `json:"list"`
}

func (h *handler) ListUserCouponUsage() core.HandlerFunc {
	return func(ctx core.Context) {
		req := new(listUserCouponUsageRequest)
		rsp := new(listUserCouponUsageResponse)
		if err := ctx.ShouldBindForm(req); err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
			return
		}
		if req.Page <= 0 {
			req.Page = 1
		}
		if req.PageSize <= 0 {
			req.PageSize = 20
		}
		if req.PageSize > 100 {
			req.PageSize = 100
		}
		userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID"))
			return
		}
		ucid, err := strconv.ParseInt(ctx.Param("user_coupon_id"), 10, 64)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递持券ID"))
			return
		}
		var total int64
		db := h.repo.GetDbR().Model(&model.UserCouponLedger{}).Where("user_id = ? AND user_coupon_id = ?", userID, ucid)
		_ = db.Count(&total).Error

		var list []model.UserCouponLedger
		_ = db.Order("id DESC").Offset((req.Page - 1) * req.PageSize).Limit(req.PageSize).Find(&list).Error

		rows := make([]adminUserCouponUsageItem, len(list))
		for i, v := range list {
			rows[i] = adminUserCouponUsageItem{
				ID:           v.ID,
				UserID:       v.UserID,
				UserCouponID: v.UserCouponID,
				ChangeAmount: v.ChangeAmount,
				BalanceAfter: v.BalanceAfter,
				OrderID:      v.OrderID,
				Action:       v.Action,
				CreatedAt:    v.CreatedAt.Format("2006-01-02 15:04:05"),
			}
		}
		rsp.Page = req.Page
		rsp.PageSize = req.PageSize
		rsp.Total = total
		if rows == nil {
			rows = []adminUserCouponUsageItem{}
		}
		rsp.List = rows
		ctx.Payload(rsp)
	}
}
