package admin

import (
	"math"
	"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           string `form:"id"`
	DouyinID     string `form:"douyin_id"`
	DouyinUserID string `form:"douyin_user_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 != "" {
			if id, err := strconv.ParseInt(req.ID, 10, 64); err == nil {
				q = q.Where(h.readDB.Users.ID.Eq(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))
			}
		}
		if req.DouyinID != "" {
			q = q.Where(h.readDB.Users.DouyinID.Like("%" + req.DouyinID + "%"))
		}
		if req.DouyinUserID != "" {
			q = q.Where(h.readDB.Users.DouyinUserID.Like("%" + req.DouyinUserID + "%"))
		}

		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] = int64(h.userSvc.CentsToPointsFloat(ctx.RequestContext(), 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.SourceType.In(2, 3, 4)). // 排除积分兑换(1)和奖品兑换(5)
				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.SourceType.In(2, 3, 4)). // 排除积分兑换(1)和奖品兑换(5)
				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.SourceType.In(2, 3, 4)). // 排除积分兑换(1)和奖品兑换(5)
				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)).
				Where(h.readDB.Orders.SourceType.In(2, 3, 4)). // 排除积分兑换(1)和奖品兑换(5)
				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
			}
		}

		// 批量查询持有商品价值
		inventoryValues := make(map[int64]int64)
		if len(userIDs) > 0 {
			type invResult struct {
				UserID int64
				Value  int64
			}
			var invRes []invResult
			h.readDB.UserInventory.WithContext(ctx.RequestContext()).ReadDB().
				LeftJoin(h.readDB.Products, h.readDB.Products.ID.EqCol(h.readDB.UserInventory.ProductID)).
				Select(h.readDB.UserInventory.UserID, h.readDB.Products.Price.Sum().As("value")).
				Where(h.readDB.UserInventory.UserID.In(userIDs...)).
				Where(h.readDB.UserInventory.Status.Eq(1)). // 1=持有
				Group(h.readDB.UserInventory.UserID).
				Scan(&invRes)
			for _, r := range invRes {
				inventoryValues[r.UserID] = r.Value
			}
		}

		// 批量查询优惠券价值（余额之和）
		couponValues := make(map[int64]int64)
		if len(userIDs) > 0 {
			type valResult struct {
				UserID int64
				Value  int64
			}
			var vRes []valResult
			h.readDB.UserCoupons.WithContext(ctx.RequestContext()).ReadDB().
				Select(h.readDB.UserCoupons.UserID, h.readDB.UserCoupons.BalanceAmount.Sum().As("value")).
				Where(h.readDB.UserCoupons.UserID.In(userIDs...)).
				Where(h.readDB.UserCoupons.Status.Eq(1)).
				Group(h.readDB.UserCoupons.UserID).
				Scan(&vRes)
			for _, v := range vRes {
				couponValues[v.UserID] = v.Value
			}
		}

		// 批量查询道具卡价值
		itemCardValues := make(map[int64]int64)
		if len(userIDs) > 0 {
			type valResult struct {
				UserID int64
				Value  int64
			}
			var vRes []valResult
			h.readDB.UserItemCards.WithContext(ctx.RequestContext()).ReadDB().
				LeftJoin(h.readDB.SystemItemCards, h.readDB.SystemItemCards.ID.EqCol(h.readDB.UserItemCards.CardID)).
				Select(h.readDB.UserItemCards.UserID, h.readDB.SystemItemCards.Price.Sum().As("value")).
				Where(h.readDB.UserItemCards.UserID.In(userIDs...)).
				Where(h.readDB.UserItemCards.Status.Eq(1)).
				Group(h.readDB.UserItemCards.UserID).
				Scan(&vRes)
			for _, v := range vRes {
				itemCardValues[v.UserID] = v.Value
			}
		}

		// 批量查询所有用户的邀请人昵称
		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 {
			pointsBal := pointBalances[v.ID]
			invVal := inventoryValues[v.ID]
			cpVal := couponValues[v.ID]
			icVal := itemCardValues[v.ID]
			gpCount := gamePassCounts[v.ID]
			gtCount := gameTicketCounts[v.ID]

			// 总资产估值逻辑：积分余额 + 商品价值 + 优惠券价值 + 道具卡价值 + 次数卡(2元/次)
			// 游戏资格不计入估值（购买其他商品赠送，无实际价值）
			assetVal := pointsBal*100 + invVal + cpVal + icVal + gpCount*200

			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,
				DouyinUserID:     v.DouyinUserID,
				Mobile:           v.Mobile,
				Remark:           v.Remark,
				ChannelName:      v.ChannelName,
				ChannelCode:      v.ChannelCode,
				PointsBalance:    pointsBal,
				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:    gpCount,
				GameTicketCount:  gtCount,
				InventoryValue:   invVal,
				TotalAssetValue:  assetVal,
				Status:           v.Status,
			}
		}
		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"`
	Keyword  string `form:"keyword"` // 搜索关键词（商品名称）
	Status   int32  `form:"status"`  // 状态筛选：0=全部, 1=持有, 2=作废, 3=已使用
}
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)
// @Param keyword query string false "搜索关键词"
// @Param status query int false "状态筛选: 0=全部, 1=持有, 2=作废, 3=已使用"
// @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
		}

		// 处理分页参数
		if req.Page <= 0 {
			req.Page = 1
		}
		if req.PageSize <= 0 {
			req.PageSize = 20
		}
		if req.PageSize > 100 {
			req.PageSize = 100
		}

		// 如果有搜索关键词，使用带搜索的查询
		if req.Keyword != "" {
			// 联表查询以支持按商品名称搜索
			ui := h.readDB.UserInventory
			p := h.readDB.Products

			// Check if keyword is numeric
			numKeyword, errNum := strconv.ParseInt(req.Keyword, 10, 64)

			// Count query logic
			countQ := h.readDB.UserInventory.WithContext(ctx.RequestContext()).ReadDB().
				LeftJoin(p, p.ID.EqCol(ui.ProductID)).
				Where(ui.UserID.Eq(userID))

			// 应用状态筛选
			if req.Status > 0 {
				countQ = countQ.Where(ui.Status.Eq(req.Status))
			} else {
				// 默认只过滤掉已软删除的记录（如果有的话，status=2是作废，通常后台要能看到作废的，所以这里如果不传status默认查所有非删除的？）
				// 既然是管理端，如果不传status，应该显示所有状态的记录
			}

			if errNum == nil {
				// Keyword is numeric, search by name OR ID OR OrderID
				countQ = countQ.Where(
					ui.Where(p.Name.Like("%" + req.Keyword + "%")).
						Or(ui.ID.Eq(numKeyword)).
						Or(ui.OrderID.Eq(numKeyword)),
				)
			} else {
				// Keyword is not numeric, search by name only
				countQ = countQ.Where(p.Name.Like("%" + req.Keyword + "%"))
			}

			total, err := countQ.Count()
			if err != nil {
				ctx.AbortWithError(core.Error(http.StatusBadRequest, 20105, err.Error()))
				return
			}

			// 查询资产数据
			type inventoryRow struct {
				ID            int64
				UserID        int64
				ProductID     int64
				OrderID       int64
				ActivityID    int64
				RewardID      int64
				Status        int32
				Remark        string
				CreatedAt     string
				UpdatedAt     string
				ProductName   string
				ProductImages string
				ProductPrice  int64
			}
			var rows []inventoryRow

			sql := `
				SELECT ui.id, ui.user_id, ui.product_id, ui.order_id, ui.activity_id, ui.reward_id, 
					   ui.status, ui.remark, ui.created_at, ui.updated_at,
					   p.name as product_name, p.images_json as product_images, p.price as product_price
				FROM user_inventory ui
				LEFT JOIN products p ON p.id = ui.product_id
				WHERE ui.user_id = ?
			`
			var args []interface{}
			args = append(args, userID)

			if req.Status > 0 {
				sql += " AND ui.status = ?"
				args = append(args, req.Status)
			}

			if errNum == nil {
				sql += " AND (p.name LIKE ? OR ui.id = ? OR ui.order_id = ?)"
				args = append(args, "%"+req.Keyword+"%", numKeyword, numKeyword)
			} else {
				sql += " AND p.name LIKE ?"
				args = append(args, "%"+req.Keyword+"%")
			}

			sql += " ORDER BY ui.id DESC LIMIT ? OFFSET ?"
			args = append(args, req.PageSize, (req.Page-1)*req.PageSize)

			err = h.repo.GetDbR().Raw(sql, args...).Scan(&rows).Error
			if err != nil {
				ctx.AbortWithError(core.Error(http.StatusBadRequest, 20105, err.Error()))
				return
			}

			// 转换结果
			items := make([]*user.InventoryWithProduct, len(rows))
			for i, r := range rows {
				items[i] = &user.InventoryWithProduct{
					UserInventory: &model.UserInventory{
						ID:         r.ID,
						UserID:     r.UserID,
						ProductID:  r.ProductID,
						OrderID:    r.OrderID,
						ActivityID: r.ActivityID,
						RewardID:   r.RewardID,
						Status:     r.Status,
						Remark:     r.Remark,
					},
					ProductName:   r.ProductName,
					ProductImages: r.ProductImages,
					ProductPrice:  r.ProductPrice,
				}
			}

			rsp.Page = req.Page
			rsp.PageSize = req.PageSize
			rsp.Total = total
			rsp.List = items
			ctx.Payload(rsp)
			return
		}

		// 无搜索关键词时使用原有逻辑
		rows, total, err := h.userSvc.ListInventoryWithProduct(ctx.RequestContext(), userID, req.Page, req.PageSize, req.Status)
		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 := base.
			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,
			).
			LeftJoin(h.readDB.SystemCoupons, h.readDB.SystemCoupons.ID.EqCol(h.readDB.UserCoupons.CouponID)).
			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)
	}
}

type AuditLogItem struct {
	CreatedAt  string `json:"created_at"`  // 时间
	Category   string `json:"category"`    // 大类: points/order/shipping/draw
	SubType    string `json:"sub_type"`    // 子类: action/status
	AmountStr  string `json:"amount_str"`  // 金额/数值变化 (带符号字符串)
	RefInfo    string `json:"ref_info"`    // 关联信息 (RefID/OrderNo/ExpressNo)
	DetailInfo string `json:"detail_info"` // 详细描述 (Remark/PrizeName)
}

type listAuditLogsResponse struct {
	Page     int            `json:"page"`
	PageSize int            `json:"page_size"`
	Total    int64          `json:"total"` // 由于UNION ALL分页较难精确Count Total，这里可能返回估算值或分步Count，为简化MVP先只做翻页不用Total或者Total设为0
	List     []AuditLogItem `json:"list"`
}

// ListUserAuditLogs 查看用户行为审计日志
// @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 "每页数量" default(20)
// @Success 200 {object} listAuditLogsResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/users/{user_id}/audit [get]
// @Security LoginVerifyToken
func (h *handler) ListUserAuditLogs() core.HandlerFunc {
	return func(ctx core.Context) {
		req := new(listInvitesRequest) // 复用分页参数结构
		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
		}
		if req.Page <= 0 {
			req.Page = 1
		}
		if req.PageSize <= 0 {
			req.PageSize = 20
		}

		offset := (req.Page - 1) * req.PageSize
		limit := req.PageSize

		var logs []AuditLogItem

		// 构建 UNION ALL 查询
		// 1. 积分流水
		// 2. 订单记录
		// 3. 发货记录
		// 4. 抽奖记录 (只看中奖的? 或者全部? 这里先只看中奖 IsWinner=1 避免数据量太大)

		sql := `
			SELECT * FROM (
				-- 1. Points Ledger
				SELECT 
					created_at, 
					'points' as category, 
					CONVERT(action USING utf8mb4) as sub_type, 
					CAST(points AS CHAR) as amount_str, 
					CONCAT(CONVERT(ref_table USING utf8mb4), ':', CONVERT(ref_id USING utf8mb4)) as ref_info, 
					CONVERT(remark USING utf8mb4) as detail_info
				FROM user_points_ledger
				WHERE user_id = ?

				UNION ALL

				-- 2. Orders
				SELECT 
					created_at, 
					'order' as category, 
					'paid' as sub_type, 
					CAST(actual_amount AS CHAR) as amount_str, 
					CONVERT(order_no USING utf8mb4) as ref_info, 
					CONVERT(remark USING utf8mb4) as detail_info
				FROM orders
				WHERE user_id = ? AND status = 2

				UNION ALL

				-- 3. Shipping Records
				SELECT 
					created_at, 
					'shipping' as category, 
					CAST(status AS CHAR) as sub_type, 
					CAST(quantity AS CHAR) as amount_str, 
					CONCAT(IFNULL(CONVERT(express_code USING utf8mb4),''), ':', IFNULL(CONVERT(express_no USING utf8mb4),'')) as ref_info, 
					CONVERT(remark USING utf8mb4) as detail_info
				FROM shipping_records
				WHERE user_id = ?

				UNION ALL

				-- 4. Draw Logs (Winners)
				SELECT 
					l.created_at,
					'draw' as category,
					IF(l.is_winner=1, 'win', 'lose') as sub_type,
					CAST(1 AS CHAR) as amount_str,
					CAST(l.order_id AS CHAR) as ref_info,
					CONCAT(
						'游戏: ', IFNULL(CONVERT(act.name USING utf8mb4), '未知'),
						' | 奖品: ', IFNULL(CONVERT(prod.name USING utf8mb4), '未知'),
						' | 级别: ', CASE l.level WHEN 1 THEN 'S' WHEN 2 THEN 'A' WHEN 3 THEN 'B' WHEN 4 THEN 'C' ELSE CAST(l.level AS CHAR) END
					) as detail_info
				FROM activity_draw_logs l
				LEFT JOIN activity_issues issue ON l.issue_id = issue.id
				LEFT JOIN activities act ON issue.activity_id = act.id
				LEFT JOIN activity_reward_settings reward ON l.reward_id = reward.id
				LEFT JOIN products prod ON reward.product_id = prod.id
				WHERE l.user_id = ? AND l.is_winner = 1
			) as combined_logs
			ORDER BY created_at DESC
			LIMIT ? OFFSET ?
		`

		if err := h.repo.GetDbR().Raw(sql, userID, userID, userID, userID, limit, offset).Scan(&logs).Error; err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 20107, err.Error()))
			return
		}

		// 格式化处理 (Optional)
		for i := range logs {
			// 将时间标准化
			if t, err := time.Parse(time.RFC3339, logs[i].CreatedAt); err == nil {
				logs[i].CreatedAt = t.Format("2006-01-02 15:04:05")
			}
			// 翻译 Shipping Status 等 (可选项，也可以前端做)
			if logs[i].Category == "shipping" {
				switch logs[i].SubType {
				case "1":
					logs[i].SubType = "待发货"
				case "2":
					logs[i].SubType = "已发货"
				case "3":
					logs[i].SubType = "已签收"
				case "4":
					logs[i].SubType = "异常"
				}
			}
		}

		ctx.Payload(&listAuditLogsResponse{
			Page:     req.Page,
			PageSize: req.PageSize,
			Total:    0, // 为了性能暂时忽略
			List:     logs,
		})
	}
}

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

type listPointsRequest struct {
	Page     int `form:"page"`
	PageSize int `form:"page_size"`
}
type adminUserPointsLedgerItem struct {
	ID        int64   `json:"id"`
	UserID    int64   `json:"user_id"`
	Action    string  `json:"action"`
	Points    float64 `json:"points"` // 改为 float64 支持小数积分
	RefTable  string  `json:"ref_table"`
	RefID     string  `json:"ref_id"`
	Remark    string  `json:"remark"`
	CreatedAt string  `json:"created_at"`
}

type listPointsResponse struct {
	Page     int                         `json:"page"`
	PageSize int                         `json:"page_size"`
	Total    int64                       `json:"total"`
	List     []adminUserPointsLedgerItem `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
		// Convert ledger items
		rsp.List = make([]adminUserPointsLedgerItem, len(items))
		for i, v := range items {
			rsp.List[i] = adminUserPointsLedgerItem{
				ID:        v.ID,
				UserID:    v.UserID,
				Action:    v.Action,
				Points:    h.userSvc.CentsToPointsFloat(ctx.RequestContext(), v.Points),
				RefTable:  v.RefTable,
				RefID:     v.RefID,
				Remark:    v.Remark,
				CreatedAt: v.CreatedAt.Format("2006-01-02 15:04:05"),
			}
		}
		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"`
	DouyinUserID     string `json:"douyin_user_id"` // 用户的抖音账号ID
	Mobile           string `json:"mobile"`         // 手机号
	Remark           string `json:"remark"`         // 备注
	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"`  // 游戏资格数量
	InventoryValue   int64  `json:"inventory_value"`    // 持有商品总价值
	TotalAssetValue  int64  `json:"total_asset_value"`  // 总资产估值
	Status           int32  `json:"status"`             // 用户状态：1正常 2禁用 3黑名单
}

// 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 = int64(h.userSvc.CentsToPointsFloat(ctx.RequestContext(), total))
		ctx.Payload(rsp)
	}
}

type addPointsRequest struct {
	Points    float64 `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
		}

		// 将浮点数积分转换为分（Cents）
		// 1 积分 = 1 元 = 100 分
		// 使用 math.Round 避免精度问题
		pointsCents := int64(math.Round(req.Points * 100))

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

		var validStart *time.Time
		var validEnd *time.Time
		now := time.Now()
		// 只有增加积分时才设置有效期
		if pointsCents > 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, pointsCents, 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)
	}
}

// LinkUserDouyinRequest 关联用户抖音账号请求
type LinkUserDouyinRequest struct {
	DouyinUserID string `json:"douyin_user_id" binding:"required"`
}

// UpdateUserDouyinID 更新用户的抖音账号ID
// @Summary 更新用户抖音ID
// @Description 管理员绑定或修改用户的抖音账号ID
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Param body body LinkUserDouyinRequest true "抖音用户ID"
// @Success 200 {object} map[string]any
// @Failure 400 {object} code.Failure
// @Router /api/admin/users/{user_id}/douyin_user_id [put]
// @Security LoginVerifyToken
func (h *handler) UpdateUserDouyinID() core.HandlerFunc {
	return func(ctx core.Context) {
		userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "用户ID无效"))
			return
		}

		req := new(LinkUserDouyinRequest)
		if err := ctx.ShouldBindJSON(req); err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
			return
		}

		// 更新用户抖音ID
		_, err = h.writeDB.Users.WithContext(ctx.RequestContext()).
			Where(h.writeDB.Users.ID.Eq(userID)).
			Update(h.writeDB.Users.DouyinUserID, req.DouyinUserID)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 20301, "更新失败: "+err.Error()))
			return
		}

		ctx.Payload(map[string]any{
			"success": true,
			"message": "抖音ID更新成功",
		})
	}
}

// updateUserRemarkRequest 更新用户备注请求
type updateUserRemarkRequest struct {
	Remark string `json:"remark"`
}

// UpdateUserRemark 更新用户备注
// @Summary 更新用户备注
// @Description 管理员修改用户备注
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Param body body updateUserRemarkRequest true "备注信息"
// @Success 200 {object} map[string]any
// @Failure 400 {object} code.Failure
// @Router /api/admin/users/{user_id}/remark [put]
// @Security LoginVerifyToken
func (h *handler) UpdateUserRemark() core.HandlerFunc {
	return func(ctx core.Context) {
		userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "用户ID无效"))
			return
		}

		req := new(updateUserRemarkRequest)
		if err := ctx.ShouldBindJSON(req); err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
			return
		}

		// 更新用户备注
		_, err = h.writeDB.Users.WithContext(ctx.RequestContext()).
			Where(h.writeDB.Users.ID.Eq(userID)).
			Update(h.writeDB.Users.Remark, req.Remark)
		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, 20302, "更新失败: "+err.Error()))
			return
		}

		ctx.Payload(map[string]any{
			"success": true,
			"message": "备注更新成功",
		})
	}
}

type updateUserStatusRequest struct {
	Status int32 `json:"status" form:"status"` // 1=正常 2=禁用 3=黑名单
}

// UpdateUserStatus 修改用户状态
// @Summary 修改用户状态
// @Description 管理员修改用户状态（1正常 2禁用 3黑名单）
// @Tags 管理端.用户
// @Accept json
// @Produce json
// @Param user_id path integer true "用户ID"
// @Param body body updateUserStatusRequest true "状态信息"
// @Success 200 {object} map[string]any
// @Failure 400 {object} code.Failure
// @Router /api/admin/users/{user_id}/status [put]
// @Security LoginVerifyToken
func (h *handler) UpdateUserStatus() core.HandlerFunc {
	return func(ctx core.Context) {
		req := new(updateUserStatusRequest)
		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 req.Status != 1 && req.Status != 2 && req.Status != 3 {
			ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "无效的状态值"))
			return
		}

		// 使用 Updates 以支持更新为 0 (虽然这里status不为0) 但 gorm Update 单列更安全
		_, err = h.writeDB.Users.WithContext(ctx.RequestContext()).
			Where(h.writeDB.Users.ID.Eq(userID)).
			Update(h.writeDB.Users.Status, req.Status)

		if err != nil {
			ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, err.Error()))
			return
		}

		ctx.Payload(map[string]any{"success": true})
	}
}
