// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package api

import (
	"context"
	"fmt"
	"math/rand"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/redis/go-redis/v9"

	"git.imall.cloud/openim/open-im-server-deploy/pkg/apistruct"
	"git.imall.cloud/openim/open-im-server-deploy/pkg/common/servererrs"
	"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
	"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database/mgo"
	"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
	"git.imall.cloud/openim/open-im-server-deploy/pkg/msgprocessor"
	"git.imall.cloud/openim/open-im-server-deploy/pkg/rpcli"
	"git.imall.cloud/openim/protocol/constant"
	"git.imall.cloud/openim/protocol/msg"
	"git.imall.cloud/openim/protocol/sdkws"
	"github.com/openimsdk/tools/apiresp"
	"github.com/openimsdk/tools/errs"
	"github.com/openimsdk/tools/log"
	"github.com/openimsdk/tools/mcontext"
	"github.com/openimsdk/tools/utils/idutil"
	"github.com/openimsdk/tools/utils/jsonutil"
	"github.com/openimsdk/tools/utils/timeutil"
)

type RedPacketApi struct {
	groupClient           *rpcli.GroupClient
	userClient            *rpcli.UserClient
	msgClient             msg.MsgClient
	redPacketDB           database.RedPacket
	redPacketReceiveDB    database.RedPacketReceive
	walletDB              database.Wallet
	walletBalanceRecordDB database.WalletBalanceRecord
	redisClient           redis.UniversalClient
}

func NewRedPacketApi(groupClient *rpcli.GroupClient, userClient *rpcli.UserClient, msgClient msg.MsgClient, redPacketDB database.RedPacket, redPacketReceiveDB database.RedPacketReceive, walletDB database.Wallet, walletBalanceRecordDB database.WalletBalanceRecord, redisClient redis.UniversalClient) *RedPacketApi {
	return &RedPacketApi{
		groupClient:           groupClient,
		userClient:            userClient,
		msgClient:             msgClient,
		redPacketDB:           redPacketDB,
		redPacketReceiveDB:    redPacketReceiveDB,
		walletDB:              walletDB,
		walletBalanceRecordDB: walletBalanceRecordDB,
		redisClient:           redisClient,
	}
}

// SendRedPacket 发送红包（只支持群聊，发送用户默认为群主）
func (r *RedPacketApi) SendRedPacket(c *gin.Context) {
	var (
		req  apistruct.SendRedPacketReq
		resp apistruct.SendRedPacketResp
	)
	if err := c.BindJSON(&req); err != nil {
		apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
		return
	}

	// 验证红包参数
	if req.TotalAmount <= 0 {
		apiresp.GinError(c, errs.ErrArgs.WrapMsg("totalAmount must be greater than 0"))
		return
	}
	if req.TotalCount <= 0 {
		apiresp.GinError(c, errs.ErrArgs.WrapMsg("totalCount must be greater than 0"))
		return
	}
	if req.RedPacketType != 1 && req.RedPacketType != 2 {
		apiresp.GinError(c, errs.ErrArgs.WrapMsg("redPacketType must be 1 (normal) or 2 (random)"))
		return
	}

	// 获取群信息，验证群是否存在
	groupInfos, err := r.groupClient.GetGroupsInfo(c, []string{req.GroupID})
	if err != nil {
		apiresp.GinError(c, err)
		return
	}
	if len(groupInfos) == 0 {
		apiresp.GinError(c, errs.ErrArgs.WrapMsg("group not found"))
		return
	}
	groupInfo := groupInfos[0]

	// 获取群主ID（发送用户默认为群主）
	sendUserID := groupInfo.OwnerUserID
	if sendUserID == "" {
		apiresp.GinError(c, errs.ErrArgs.WrapMsg("group owner not found"))
		return
	}

	// 获取群主用户信息（用于设置发送者昵称和头像）
	userInfos, err := r.userClient.GetUsersInfo(c, []string{sendUserID})
	if err != nil {
		log.ZWarn(c, "SendRedPacket: failed to get user info", err, "sendUserID", sendUserID)
		// 如果获取用户信息失败，继续发送，但不设置昵称和头像
	}

	var senderNickname, senderFaceURL string
	if len(userInfos) > 0 && userInfos[0] != nil {
		senderNickname = userInfos[0].Nickname
		senderFaceURL = userInfos[0].FaceURL
	}

	// 生成红包ID
	redPacketID := idutil.GetMsgIDByMD5(sendUserID + req.GroupID + timeutil.GetCurrentTimeFormatted())

	// 计算会话ID
	conversationID := msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, req.GroupID)

	// 计算过期时间（默认24小时）
	expireTime := time.Now().Add(24 * time.Hour)

	// 初始化Redis数据结构（所有红包类型都需要）
	if r.redisClient == nil {
		apiresp.GinError(c, errs.ErrInternalServer.WrapMsg("redis client is not available"))
		return
	}

	ctx := context.Background()
	queueKey := r.getRedPacketQueueKey(redPacketID)
	usersKey := r.getRedPacketUsersKey(redPacketID)
	expireDuration := 24 * time.Hour

	if req.RedPacketType == model.RedPacketTypeRandom {
		// 拼手气红包：预先分配金额并推送到Redis队列
		amounts, err := r.allocateRandomAmounts(req.TotalAmount, req.TotalCount)
		if err != nil {
			log.ZError(c, "SendRedPacket: failed to allocate random amounts", err, "redPacketID", redPacketID, "groupID", req.GroupID)
			apiresp.GinError(c, errs.ErrInternalServer.WrapMsg("failed to allocate random amounts"))
			return
		}

		pipe := r.redisClient.Pipeline()
		for _, amount := range amounts {
			pipe.RPush(ctx, queueKey, strconv.FormatInt(amount, 10))
		}
		pipe.Expire(ctx, queueKey, expireDuration)
		pipe.Expire(ctx, usersKey, expireDuration)
		if _, err := pipe.Exec(ctx); err != nil {
			log.ZError(c, "SendRedPacket: failed to push amounts to redis queue", err, "redPacketID", redPacketID, "groupID", req.GroupID)
			apiresp.GinError(c, errs.ErrInternalServer.WrapMsg("failed to push amounts to redis queue"))
			return
		}
		log.ZInfo(c, "SendRedPacket: pushed amounts to redis queue", "redPacketID", redPacketID, "count", len(amounts))
	} else {
		// 普通红包：初始化队列，每个元素为平均金额
		avgAmount := req.TotalAmount / int64(req.TotalCount)
		pipe := r.redisClient.Pipeline()
		for i := int32(0); i < req.TotalCount; i++ {
			pipe.RPush(ctx, queueKey, strconv.FormatInt(avgAmount, 10))
		}
		pipe.Expire(ctx, queueKey, expireDuration)
		pipe.Expire(ctx, usersKey, expireDuration)
		if _, err := pipe.Exec(ctx); err != nil {
			log.ZError(c, "SendRedPacket: failed to initialize redis queue", err, "redPacketID", redPacketID, "groupID", req.GroupID)
			apiresp.GinError(c, errs.ErrInternalServer.WrapMsg("failed to initialize redis queue"))
			return
		}
		log.ZInfo(c, "SendRedPacket: initialized redis queue for normal red packet", "redPacketID", redPacketID, "count", req.TotalCount, "avgAmount", avgAmount)
	}

	// 创建红包数据库记录
	redPacketRecord := &model.RedPacket{
		RedPacketID:    redPacketID,
		SendUserID:     sendUserID,
		GroupID:        req.GroupID,
		ConversationID: conversationID,
		SessionType:    constant.ReadGroupChatType,
		RedPacketType:  req.RedPacketType,
		TotalAmount:    req.TotalAmount,
		TotalCount:     req.TotalCount,
		RemainAmount:   req.TotalAmount,
		RemainCount:    req.TotalCount,
		Blessing:       req.Blessing,
		Status:         model.RedPacketStatusActive,
		ExpireTime:     expireTime,
		CreateTime:     time.Now(),
	}

	// 保存红包记录到数据库
	if err := r.redPacketDB.Create(c, redPacketRecord); err != nil {
		log.ZError(c, "SendRedPacket: failed to create red packet record", err, "redPacketID", redPacketID, "groupID", req.GroupID)
		apiresp.GinError(c, errs.ErrInternalServer.WrapMsg("failed to create red packet record"))
		return
	}

	// 创建红包消息内容（使用自定义消息格式）
	redPacketElem := apistruct.RedPacketElem{
		RedPacketID:   redPacketID,
		RedPacketType: req.RedPacketType,
		Blessing:      req.Blessing,
	}
	// 将红包数据序列化为JSON字符串，存储在data字段中
	redPacketData := jsonutil.StructToJsonString(redPacketElem)

	// 构建自定义消息结构
	customElem := apistruct.CustomElem{
		Data:        redPacketData,
		Description: "redpacket", // 二级类型标识：红包消息
		Extension:   "",          // 扩展字段，可用于未来扩展
	}
	content := jsonutil.StructToJsonString(customElem)

	// 构建消息请求
	msgData := &sdkws.MsgData{
		SendID:           sendUserID,
		GroupID:          req.GroupID,
		ClientMsgID:      idutil.GetMsgIDByMD5(sendUserID),
		SenderPlatformID: 0, // 系统发送
		SenderNickname:   senderNickname,
		SenderFaceURL:    senderFaceURL,
		SessionType:      constant.ReadGroupChatType,
		MsgFrom:          constant.SysMsgType,
		ContentType:      constant.Custom, // 使用自定义消息类型（110）
		CreateTime:       timeutil.GetCurrentTimestampByMill(),
		SendTime:         timeutil.GetCurrentTimestampByMill(),
		Content:          []byte(content),
		Options:          make(map[string]bool),
	}

	// 发送消息
	sendMsgReq := &msg.SendMsgReq{
		MsgData: msgData,
	}
	sendMsgResp, err := r.msgClient.SendMsg(c, sendMsgReq)
	if err != nil {
		log.ZError(c, "SendRedPacket: failed to send message", err, "redPacketID", redPacketID, "groupID", req.GroupID)
		apiresp.GinError(c, err)
		return
	}

	// 返回响应
	resp.RedPacketID = redPacketID
	resp.ServerMsgID = sendMsgResp.ServerMsgID
	resp.ClientMsgID = msgData.ClientMsgID
	resp.SendTime = sendMsgResp.SendTime

	log.ZInfo(c, "SendRedPacket: success", "redPacketID", redPacketID, "groupID", req.GroupID, "sendUserID", sendUserID)
	apiresp.GinSuccess(c, resp)
}

// allocateRandomAmounts 分配拼手气红包金额（仿照微信红包算法）
// 算法原理：
// 1. 确保每个红包至少有最小金额（1分）
// 2. 对于前n-1个红包，每次分配时动态计算最大可分配金额
// 3. 在最小金额和最大金额之间随机分配
// 4. 最后一个红包直接分配剩余金额
// 5. 打乱顺序增加随机性
func (r *RedPacketApi) allocateRandomAmounts(totalAmount int64, totalCount int32) ([]int64, error) {
	if totalCount <= 0 {
		return nil, errs.ErrArgs.WrapMsg("totalCount must be greater than 0")
	}
	if totalAmount < int64(totalCount) {
		return nil, errs.ErrArgs.WrapMsg("totalAmount must be at least totalCount (minimum 1 cent per packet)")
	}

	const minAmount = 1 // 最小金额：1分
	amounts := make([]int64, totalCount)
	remainAmount := totalAmount
	remainCount := totalCount

	// 使用随机种子
	rand.Seed(time.Now().UnixNano())

	// 分配前n-1个红包
	for i := int32(0); i < totalCount-1; i++ {
		// 计算最大可分配金额：剩余金额 / 剩余红包数 * 2
		// 这样可以保证后续红包还能分配，避免出现0或全部的情况
		// 但需要确保至少是最小金额
		maxAmount := remainAmount / int64(remainCount) * 2
		if maxAmount < minAmount {
			maxAmount = minAmount
		}

		// 如果剩余金额不足以分配给剩余红包（每个至少1分），则直接分配剩余金额
		if remainAmount <= int64(remainCount)*minAmount {
			amounts[i] = minAmount
			remainAmount -= minAmount
			remainCount--
			continue
		}

		// 确保最大金额不超过剩余金额减去剩余红包的最小金额
		// 这样可以保证后续红包至少能分配到最小金额
		maxAllowed := remainAmount - int64(remainCount-1)*minAmount
		if maxAmount > maxAllowed {
			maxAmount = maxAllowed
		}

		// 在最小金额和最大金额之间随机分配
		// 如果最大金额等于最小金额，直接使用最小金额
		var amount int64
		if maxAmount <= minAmount {
			amount = minAmount
		} else {
			amount = rand.Int63n(maxAmount-minAmount+1) + minAmount
		}

		amounts[i] = amount
		remainAmount -= amount
		remainCount--
	}

	// 最后一个红包，直接分配剩余金额
	amounts[totalCount-1] = remainAmount

	// 验证：确保每个红包至少1分
	if amounts[totalCount-1] < minAmount {
		return nil, errs.ErrInternalServer.WrapMsg("last packet amount is less than minimum")
	}

	// 打乱顺序（增加随机性）
	rand.Shuffle(len(amounts), func(i, j int) {
		amounts[i], amounts[j] = amounts[j], amounts[i]
	})

	// 最终验证：确保每个红包至少1分，总和等于总金额
	var sum int64
	for i := int32(0); i < totalCount; i++ {
		if amounts[i] < minAmount {
			return nil, errs.ErrInternalServer.WrapMsg("allocated amount is less than minimum")
		}
		sum += amounts[i]
	}
	if sum != totalAmount {
		return nil, errs.ErrInternalServer.WrapMsg("allocated amount sum mismatch")
	}

	return amounts, nil
}

// writeRedPacketRecordToMongoDB 异步写入MongoDB领取记录
func (r *RedPacketApi) writeRedPacketRecordToMongoDB(ctx context.Context, redPacketID, userID string, amount int64) {
	receiveID := idutil.GetMsgIDByMD5(userID + redPacketID + timeutil.GetCurrentTimeFormatted())
	receiveRecord := &model.RedPacketReceive{
		ReceiveID:     receiveID,
		RedPacketID:   redPacketID,
		ReceiveUserID: userID,
		Amount:        amount,
		ReceiveTime:   time.Now(),
		IsLucky:       false,
	}

	if err := r.redPacketReceiveDB.Create(ctx, receiveRecord); err != nil {
		// 检查是否是唯一索引冲突（MongoDB E11000错误）
		errStr := err.Error()
		if errs.ErrArgs.Is(err) || (len(errStr) > 0 && (errStr == "E11000 duplicate key error" ||
			errStr == "duplicate key error collection" ||
			len(errStr) > 5 && errStr[:5] == "E11000")) {
			log.ZDebug(ctx, "ReceiveRedPacket: receive record already exists (duplicate key)", "redPacketID", redPacketID, "userID", userID)
			return
		}
		// 其他错误记录日志，后续补偿机制会处理
		log.ZError(ctx, "ReceiveRedPacket: failed to write receive record to MongoDB", err,
			"redPacketID", redPacketID,
			"userID", userID,
			"amount", amount)
	}
}

// updateWalletBalanceAsync 异步更新钱包余额
func (r *RedPacketApi) updateWalletBalanceAsync(ctx context.Context, userID string, amount int64, redPacketID string) {
	maxRetries := 3
	var updateSuccess bool

	for retry := 0; retry < maxRetries; retry++ {
		// 查询或创建用户钱包
		wallet, err := r.walletDB.Take(ctx, userID)
		if err != nil {
			// 如果钱包不存在，创建新钱包
			if errs.ErrRecordNotFound.Is(err) || mgo.IsNotFound(err) {
				wallet = &model.Wallet{
					UserID:     userID,
					Balance:    0,
					Version:    1,
					CreateTime: time.Now(),
					UpdateTime: time.Now(),
				}
				if err := r.walletDB.Create(ctx, wallet); err != nil {
					log.ZError(ctx, "ReceiveRedPacket: failed to create wallet", err, "userID", userID, "retry", retry)
					// 如果创建失败，可能是并发创建，下次重试会查到
					if retry < maxRetries-1 {
						time.Sleep(time.Millisecond * 50)
						continue
					}
					return
				}
			} else {
				log.ZError(ctx, "ReceiveRedPacket: failed to get wallet", err, "userID", userID, "retry", retry)
				if retry < maxRetries-1 {
					time.Sleep(time.Millisecond * 50)
					continue
				}
				return
			}
		}

		// 使用版本号更新余额（防止并发覆盖）
		params := &database.WalletUpdateParams{
			UserID:     userID,
			Operation:  "add",
			Amount:     amount,
			OldBalance: wallet.Balance,
			OldVersion: wallet.Version,
		}
		result, err := r.walletDB.UpdateBalanceWithVersion(ctx, params)
		if err != nil {
			// 如果是并发冲突（版本号不匹配），重试
			log.ZWarn(ctx, "ReceiveRedPacket: concurrent modification detected, retrying", err,
				"userID", userID,
				"retry", retry+1,
				"maxRetries", maxRetries)
			if retry < maxRetries-1 {
				time.Sleep(time.Millisecond * 100)
				continue
			}
			log.ZError(ctx, "ReceiveRedPacket: failed to update wallet balance after retries", err, "userID", userID, "amount", amount)
			return
		}

		// 更新成功，创建余额记录
		updateSuccess = true

		// 创建余额记录
		recordID := idutil.GetMsgIDByMD5(userID + timeutil.GetCurrentTimeFormatted() + "add" + strconv.FormatInt(amount, 10) + redPacketID)
		balanceRecord := &model.WalletBalanceRecord{
			ID:            recordID,
			UserID:        userID,
			Amount:        amount, // 领取红包金额（正数）
			Type:          8,      // 8 = 抢红包
			BeforeBalance: params.OldBalance,
			AfterBalance:  result.NewBalance,
			OrderID:       "",
			TransactionID: "",
			RedPacketID:   redPacketID,
			Remark:        "领取红包",
			CreateTime:    time.Now(),
		}
		if err := r.walletBalanceRecordDB.Create(ctx, balanceRecord); err != nil {
			log.ZWarn(ctx, "ReceiveRedPacket: failed to create balance record", err,
				"userID", userID,
				"redPacketID", redPacketID,
				"amount", amount)
		}

		log.ZInfo(ctx, "ReceiveRedPacket: wallet balance updated async",
			"userID", userID,
			"oldBalance", wallet.Balance,
			"newBalance", result.NewBalance,
			"amount", amount)
		break
	}

	if !updateSuccess {
		log.ZError(ctx, "ReceiveRedPacket: failed to update wallet balance after all retries", nil, "userID", userID, "maxRetries", maxRetries)
	}
}

// writeToCompensationStream 写入失败补偿Stream（Redis Stream）
func (r *RedPacketApi) writeToCompensationStream(ctx context.Context, redPacketID, userID string, amount int64) {
	if r.redisClient == nil {
		return
	}

	streamKey := r.getRedPacketStreamKey(redPacketID)
	// 使用 XADD 写入Stream，字段包含必要信息
	values := map[string]interface{}{
		"userID": userID,
		"amount": amount,
		"time":   time.Now().Unix(),
	}

	if err := r.redisClient.XAdd(ctx, &redis.XAddArgs{
		Stream: streamKey,
		Values: values,
	}).Err(); err != nil {
		log.ZWarn(ctx, "ReceiveRedPacket: failed to write to compensation stream", err,
			"redPacketID", redPacketID,
			"userID", userID)
	}
}

// ReceiveRedPacket 领取红包（新方案：Redis负责并发控制，MongoDB异步写入）
func (r *RedPacketApi) ReceiveRedPacket(c *gin.Context) {
	var (
		req  apistruct.ReceiveRedPacketReq
		resp apistruct.ReceiveRedPacketResp
	)
	if err := c.BindJSON(&req); err != nil {
		apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
		return
	}

	// 获取当前用户ID
	opUserID := mcontext.GetOpUserID(c)
	if opUserID == "" {
		apiresp.GinError(c, errs.ErrArgs.WrapMsg("userID is required"))
		return
	}

	// 查询红包基本信息（用于状态检查）
	redPacket, err := r.redPacketDB.Take(c, req.RedPacketID)
	if err != nil {
		log.ZError(c, "ReceiveRedPacket: failed to get red packet", err, "redPacketID", req.RedPacketID, "userID", opUserID)
		apiresp.GinError(c, errs.ErrArgs.WrapMsg("red packet not found"))
		return
	}

	// 检查红包状态（过期状态检查）
	if redPacket.Status == model.RedPacketStatusExpired {
		apiresp.GinError(c, servererrs.ErrRedPacketExpired)
		return
	}

	// 【核心】使用Redis Lua脚本原子性地抢红包
	ctx := context.Background()
	amount, err := r.grabRedPacketFromRedis(ctx, req.RedPacketID, opUserID)
	if err != nil {
		if servererrs.ErrRedPacketAlreadyReceived.Is(err) {
			apiresp.GinError(c, err)
			return
		}
		if servererrs.ErrRedPacketFinished.Is(err) {
			apiresp.GinError(c, err)
			return
		}
		// 其他错误（可能是Lua脚本执行失败或返回值解析失败）
		log.ZError(c, "ReceiveRedPacket: failed to grab red packet from redis", err,
			"redPacketID", req.RedPacketID,
			"userID", opUserID,
			"error", err.Error())
		apiresp.GinError(c, errs.ErrInternalServer.WrapMsg("failed to grab red packet: "+err.Error()))
		return
	}

	// Redis抢红包成功，立即返回结果
	resp.RedPacketID = req.RedPacketID
	resp.Amount = amount
	resp.IsLucky = false

	log.ZInfo(c, "ReceiveRedPacket: successfully grabbed from redis", "redPacketID", req.RedPacketID, "userID", opUserID, "amount", amount)
	apiresp.GinSuccess(c, resp)

	// 【异步写入MongoDB】Redis成功后，异步写入MongoDB和补偿Stream
	go func() {
		// 写入失败补偿Stream（用于后续补偿）
		r.writeToCompensationStream(ctx, req.RedPacketID, opUserID, amount)

		// 异步写入MongoDB领取记录
		r.writeRedPacketRecordToMongoDB(ctx, req.RedPacketID, opUserID, amount)

		// 异步更新钱包余额
		r.updateWalletBalanceAsync(ctx, opUserID, amount, req.RedPacketID)
	}()
}

// getRedPacketQueueKey 获取红包队列的Redis key
func (r *RedPacketApi) getRedPacketQueueKey(redPacketID string) string {
	return "rp:" + redPacketID + ":list"
}

// getRedPacketUsersKey 获取已领取用户Set的Redis key
func (r *RedPacketApi) getRedPacketUsersKey(redPacketID string) string {
	return "rp:" + redPacketID + ":users"
}

// getRedPacketStreamKey 获取红包领取日志Stream的Redis key（用于失败补偿）
func (r *RedPacketApi) getRedPacketStreamKey(redPacketID string) string {
	return "rp:" + redPacketID + ":stream"
}

// grabRedPacketLuaScript Redis Lua脚本：原子性地抢红包
// 返回值：
//
//	-1: 用户已领取
//	-2: 红包已领完
//	-3: 金额解析失败（数据异常）
//	>0: 领取成功，返回金额（分）
var grabRedPacketLuaScript = redis.NewScript(`
-- KEYS[1] 红包金额 list (rp:{packetId}:list)
-- KEYS[2] 已抢用户 set (rp:{packetId}:users)
-- ARGV[1] userId

-- 已抢判断
if redis.call('SISMEMBER', KEYS[2], ARGV[1]) == 1 then
    return -1
end

-- 抢红包
local money = redis.call('LPOP', KEYS[1])
if not money then
    return -2
end

-- 将字符串金额转换为数字
local amount = tonumber(money)
if not amount then
    return -3
end

-- 记录用户（在返回之前记录，确保原子性）
redis.call('SADD', KEYS[2], ARGV[1])

-- 返回数字金额
return amount
`)

// grabRedPacketFromRedis 使用Redis Lua脚本原子性地抢红包
func (r *RedPacketApi) grabRedPacketFromRedis(ctx context.Context, redPacketID, userID string) (int64, error) {
	if r.redisClient == nil {
		return 0, errs.ErrInternalServer.WrapMsg("redis client is not available")
	}

	listKey := r.getRedPacketQueueKey(redPacketID)
	usersKey := r.getRedPacketUsersKey(redPacketID)

	// 执行Lua脚本
	result, err := grabRedPacketLuaScript.Eval(ctx, r.redisClient, []string{listKey, usersKey}, userID).Result()
	if err != nil {
		log.ZError(ctx, "ReceiveRedPacket: lua script execution failed", err,
			"redPacketID", redPacketID,
			"userID", userID,
			"listKey", listKey,
			"usersKey", usersKey)
		return 0, errs.Wrap(err)
	}

	// 记录原始返回值，用于调试
	log.ZDebug(ctx, "ReceiveRedPacket: lua script result", "redPacketID", redPacketID, "userID", userID, "result", result, "resultType", fmt.Sprintf("%T", result))

	// 解析返回值（Lua脚本返回的是数字，但Redis可能返回字符串或数字）
	var code int64
	switch v := result.(type) {
	case int64:
		code = v
	case int:
		code = int64(v)
	case string:
		// 如果返回字符串，尝试转换为数字
		parsed, err := strconv.ParseInt(v, 10, 64)
		if err != nil {
			log.ZError(ctx, "ReceiveRedPacket: failed to parse lua script return value", err, "redPacketID", redPacketID, "userID", userID, "result", result)
			return 0, errs.ErrInternalServer.WrapMsg("invalid lua script return value: " + v)
		}
		code = parsed
	default:
		log.ZError(ctx, "ReceiveRedPacket: unexpected lua script return type", nil, "redPacketID", redPacketID, "userID", userID, "result", result, "type", fmt.Sprintf("%T", result))
		return 0, errs.ErrInternalServer.WrapMsg(fmt.Sprintf("invalid lua script return value type: %T", result))
	}

	switch code {
	case -1:
		return 0, servererrs.ErrRedPacketAlreadyReceived
	case -2:
		return 0, servererrs.ErrRedPacketFinished
	case -3:
		return 0, errs.ErrInternalServer.WrapMsg("invalid red packet amount data")
	default:
		// code > 0 表示领取成功，返回金额
		return code, nil
	}
}

// paginationWrapper 实现 pagination.Pagination 接口
type paginationWrapper struct {
	pageNumber int32
	showNumber int32
}

func (p *paginationWrapper) GetPageNumber() int32 {
	if p.pageNumber <= 0 {
		return 1
	}
	return p.pageNumber
}

func (p *paginationWrapper) GetShowNumber() int32 {
	if p.showNumber <= 0 {
		return 20
	}
	return p.showNumber
}

// GetRedPacketsByGroup 根据群ID查询红包列表（后台管理接口）
func (r *RedPacketApi) GetRedPacketsByGroup(c *gin.Context) {
	var (
		req  apistruct.GetRedPacketsByGroupReq
		resp apistruct.GetRedPacketsByGroupResp
	)
	if err := c.BindJSON(&req); err != nil {
		apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
		return
	}

	// 设置默认分页参数
	if req.Pagination.PageNumber <= 0 {
		req.Pagination.PageNumber = 1
	}
	if req.Pagination.ShowNumber <= 0 {
		req.Pagination.ShowNumber = 20
	}

	// 创建分页对象
	pagination := &paginationWrapper{
		pageNumber: req.Pagination.PageNumber,
		showNumber: req.Pagination.ShowNumber,
	}

	// 查询红包列表
	var total int64
	var redPackets []*model.RedPacket
	var err error
	if req.GroupID == "" {
		// 如果群ID为空，查询所有红包
		total, redPackets, err = r.redPacketDB.FindAllRedPackets(c, pagination)
	} else {
		// 如果群ID不为空，查询指定群的红包
		total, redPackets, err = r.redPacketDB.FindRedPacketsByGroup(c, req.GroupID, pagination)
	}
	if err != nil {
		log.ZError(c, "GetRedPacketsByGroup: failed to find red packets", err, "groupID", req.GroupID)
		apiresp.GinError(c, errs.ErrInternalServer.WrapMsg("failed to find red packets"))
		return
	}

	// 收集所有唯一的群ID
	groupIDMap := make(map[string]bool)
	for _, rp := range redPackets {
		if rp.GroupID != "" {
			groupIDMap[rp.GroupID] = true
		}
	}

	// 批量查询群信息
	groupIDList := make([]string, 0, len(groupIDMap))
	for groupID := range groupIDMap {
		groupIDList = append(groupIDList, groupID)
	}

	groupInfoMap := make(map[string]string) // groupID -> groupName
	if len(groupIDList) > 0 {
		groupInfos, err := r.groupClient.GetGroupsInfo(c, groupIDList)
		if err == nil && groupInfos != nil {
			for _, groupInfo := range groupInfos {
				groupInfoMap[groupInfo.GroupID] = groupInfo.GroupName
			}
		} else {
			log.ZWarn(c, "GetRedPacketsByGroup: failed to get groups info", err, "groupIDs", groupIDList)
		}
	}

	// 转换为响应格式
	resp.Total = total
	resp.RedPackets = make([]*apistruct.RedPacketInfo, 0, len(redPackets))
	for _, rp := range redPackets {
		groupName := groupInfoMap[rp.GroupID]
		resp.RedPackets = append(resp.RedPackets, &apistruct.RedPacketInfo{
			RedPacketID:   rp.RedPacketID,
			SendUserID:    rp.SendUserID,
			GroupID:       rp.GroupID,
			GroupName:     groupName,
			RedPacketType: rp.RedPacketType,
			TotalAmount:   rp.TotalAmount,
			TotalCount:    rp.TotalCount,
			RemainAmount:  rp.RemainAmount,
			RemainCount:   rp.RemainCount,
			Blessing:      rp.Blessing,
			Status:        rp.Status,
			ExpireTime:    rp.ExpireTime.UnixMilli(),
			CreateTime:    rp.CreateTime.UnixMilli(),
		})
	}

	log.ZInfo(c, "GetRedPacketsByGroup: success", "groupID", req.GroupID, "total", total, "count", len(resp.RedPackets))
	apiresp.GinSuccess(c, resp)
}

// GetRedPacketReceiveInfo 查询红包领取情况（后台管理接口）
func (r *RedPacketApi) GetRedPacketReceiveInfo(c *gin.Context) {
	var (
		req  apistruct.GetRedPacketReceiveInfoReq
		resp apistruct.GetRedPacketReceiveInfoResp
	)
	if err := c.BindJSON(&req); err != nil {
		apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
		return
	}

	// 查询红包信息
	redPacket, err := r.redPacketDB.Take(c, req.RedPacketID)
	if err != nil {
		log.ZError(c, "GetRedPacketReceiveInfo: failed to get red packet", err, "redPacketID", req.RedPacketID)
		apiresp.GinError(c, errs.ErrArgs.WrapMsg("red packet not found"))
		return
	}

	// 查询领取记录
	receives, err := r.redPacketReceiveDB.FindByRedPacketID(c, req.RedPacketID)
	if err != nil {
		log.ZError(c, "GetRedPacketReceiveInfo: failed to find receives", err, "redPacketID", req.RedPacketID)
		apiresp.GinError(c, errs.ErrInternalServer.WrapMsg("failed to find receives"))
		return
	}

	// 填充响应数据
	resp.RedPacketID = redPacket.RedPacketID
	resp.TotalAmount = redPacket.TotalAmount
	resp.TotalCount = redPacket.TotalCount
	resp.RemainAmount = redPacket.RemainAmount
	resp.RemainCount = redPacket.RemainCount
	resp.Status = redPacket.Status
	resp.Receives = make([]*apistruct.RedPacketReceiveDetail, 0, len(receives))
	for _, rec := range receives {
		resp.Receives = append(resp.Receives, &apistruct.RedPacketReceiveDetail{
			ReceiveID:     rec.ReceiveID,
			ReceiveUserID: rec.ReceiveUserID,
			Amount:        rec.Amount,
			ReceiveTime:   rec.ReceiveTime.UnixMilli(),
			IsLucky:       false, // 已去掉手气最佳功能，始终返回 false
		})
	}

	log.ZInfo(c, "GetRedPacketReceiveInfo: success", "redPacketID", req.RedPacketID, "receiveCount", len(resp.Receives))
	apiresp.GinSuccess(c, resp)
}

// PauseRedPacket 暂停红包（后台管理接口）- 清空Redis队列
func (r *RedPacketApi) PauseRedPacket(c *gin.Context) {
	var (
		req  apistruct.PauseRedPacketReq
		resp apistruct.PauseRedPacketResp
	)
	if err := c.BindJSON(&req); err != nil {
		apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
		return
	}

	// 查询红包信息，验证红包是否存在
	redPacket, err := r.redPacketDB.Take(c, req.RedPacketID)
	if err != nil {
		log.ZError(c, "PauseRedPacket: failed to get red packet", err, "redPacketID", req.RedPacketID)
		apiresp.GinError(c, errs.ErrArgs.WrapMsg("red packet not found"))
		return
	}

	// 只有拼手气红包才有Redis队列，需要清空
	if redPacket.RedPacketType == model.RedPacketTypeRandom {
		if r.redisClient == nil {
			apiresp.GinError(c, errs.ErrInternalServer.WrapMsg("redis client is not available"))
			return
		}

		// 获取Redis队列key
		queueKey := r.getRedPacketQueueKey(req.RedPacketID)
		ctx := context.Background()

		// 清空Redis队列（删除整个key）
		err = r.redisClient.Del(ctx, queueKey).Err()
		if err != nil {
			log.ZError(c, "PauseRedPacket: failed to delete redis queue", err, "redPacketID", req.RedPacketID, "queueKey", queueKey)
			apiresp.GinError(c, errs.ErrInternalServer.WrapMsg("failed to pause red packet"))
			return
		}

		log.ZInfo(c, "PauseRedPacket: cleared redis queue", "redPacketID", req.RedPacketID, "queueKey", queueKey)
	} else {
		// 普通红包没有Redis队列，直接返回成功
		log.ZInfo(c, "PauseRedPacket: normal red packet, no redis queue to clear", "redPacketID", req.RedPacketID)
	}

	// 返回响应
	resp.RedPacketID = req.RedPacketID

	log.ZInfo(c, "PauseRedPacket: success", "redPacketID", req.RedPacketID)
	apiresp.GinSuccess(c, resp)
}

// GetRedPacketDetail 查询红包详情（用户端接口）
func (r *RedPacketApi) GetRedPacketDetail(c *gin.Context) {
	var (
		req  apistruct.GetRedPacketDetailReq
		resp apistruct.GetRedPacketDetailResp
	)
	if err := c.BindJSON(&req); err != nil {
		apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
		return
	}

	// 获取当前用户ID
	opUserID := mcontext.GetOpUserID(c)
	if opUserID == "" {
		apiresp.GinError(c, errs.ErrArgs.WrapMsg("userID is required"))
		return
	}

	// 查询红包信息
	redPacket, err := r.redPacketDB.Take(c, req.RedPacketID)
	if err != nil {
		log.ZError(c, "GetRedPacketDetail: failed to get red packet", err, "redPacketID", req.RedPacketID, "userID", opUserID)
		apiresp.GinError(c, errs.ErrArgs.WrapMsg("red packet not found"))
		return
	}

	// 验证用户是否在群里
	groupMember, err := r.groupClient.GetGroupMemberInfo(c, redPacket.GroupID, opUserID)
	if err != nil || groupMember == nil {
		log.ZWarn(c, "GetRedPacketDetail: user not in group", err, "redPacketID", req.RedPacketID, "groupID", redPacket.GroupID, "userID", opUserID)
		apiresp.GinError(c, errs.ErrNoPermission.WrapMsg("user not in group"))
		return
	}

	// 判断是否过期（超过一周）
	oneWeekAgo := time.Now().Add(-7 * 24 * time.Hour)
	isExpired := redPacket.CreateTime.Before(oneWeekAgo)

	// 查询当前用户的领取信息
	myReceive, err := r.redPacketReceiveDB.FindByUserAndRedPacketID(c, opUserID, req.RedPacketID)
	var myReceiveDetail *apistruct.RedPacketMyReceiveDetail
	if err == nil && myReceive != nil {
		// 自己的领取信息不返回用户ID、昵称、头像
		myReceiveDetail = &apistruct.RedPacketMyReceiveDetail{
			ReceiveID:   myReceive.ReceiveID,
			Amount:      myReceive.Amount,
			ReceiveTime: myReceive.ReceiveTime.UnixMilli(),
			IsLucky:     false, // 已去掉手气最佳功能，始终返回 false
		}
	}

	// 判断是否是群主或管理员
	isOwnerOrAdmin := groupMember.RoleLevel == constant.GroupOwner || groupMember.RoleLevel == constant.GroupAdmin

	// 构建响应
	resp.RedPacketID = redPacket.RedPacketID
	resp.GroupID = redPacket.GroupID
	resp.RedPacketType = redPacket.RedPacketType
	resp.TotalAmount = redPacket.TotalAmount
	resp.TotalCount = redPacket.TotalCount
	resp.RemainAmount = redPacket.RemainAmount
	resp.RemainCount = redPacket.RemainCount
	resp.Blessing = redPacket.Blessing
	resp.Status = redPacket.Status
	resp.IsExpired = isExpired
	resp.MyReceive = myReceiveDetail
	resp.Receives = []*apistruct.RedPacketUserReceiveDetail{}

	// 如果是群主或管理员，返回所有领取记录
	if isOwnerOrAdmin {
		receives, err := r.redPacketReceiveDB.FindByRedPacketID(c, req.RedPacketID)
		if err != nil {
			log.ZWarn(c, "GetRedPacketDetail: failed to find receives", err, "redPacketID", req.RedPacketID)
		} else {
			// 收集所有领取者ID
			userIDs := make([]string, 0, len(receives))
			for _, rec := range receives {
				userIDs = append(userIDs, rec.ReceiveUserID)
			}

			// 批量获取用户信息
			userInfos, err := r.userClient.GetUsersInfo(c, userIDs)
			if err != nil {
				log.ZWarn(c, "GetRedPacketDetail: failed to get users info", err, "userIDs", userIDs)
			}

			// 构建用户信息映射
			userInfoMap := make(map[string]*sdkws.UserInfo)
			if userInfos != nil {
				for _, userInfo := range userInfos {
					if userInfo != nil {
						userInfoMap[userInfo.UserID] = userInfo
					}
				}
			}

			// 构建领取记录列表
			resp.Receives = make([]*apistruct.RedPacketUserReceiveDetail, 0, len(receives))
			for _, rec := range receives {
				userInfo := userInfoMap[rec.ReceiveUserID]
				nickname := rec.ReceiveUserID
				faceURL := ""
				if userInfo != nil {
					nickname = userInfo.Nickname
					faceURL = userInfo.FaceURL
				}
				resp.Receives = append(resp.Receives, &apistruct.RedPacketUserReceiveDetail{
					ReceiveID:     rec.ReceiveID,
					ReceiveUserID: rec.ReceiveUserID,
					Nickname:      nickname,
					FaceURL:       faceURL,
					Amount:        rec.Amount,
					ReceiveTime:   rec.ReceiveTime.UnixMilli(),
					IsLucky:       false, // 已去掉手气最佳功能，始终返回 false
				})
			}
		}
	}

	log.ZInfo(c, "GetRedPacketDetail: success", "redPacketID", req.RedPacketID, "userID", opUserID, "isOwnerOrAdmin", isOwnerOrAdmin, "isExpired", isExpired)
	apiresp.GinSuccess(c, resp)
}
