package rankings

import (
	"context"
	"fmt"
	"github.com/redis/go-redis/v9"
	"strconv"
)

// Ranking 排行榜结构体
type Ranking struct {
	c *redis.Client
}

const EcpmRankingsListPrefix = "ecpm:rankings:"

// NewRanking 创建一个新的排行榜实例
func NewRanking(c *redis.Client) *Ranking {
	return &Ranking{
		c: c,
	}
}

func GetRankingsCacheKey(appId uint32, t uint32) string {
	return fmt.Sprintf("%sappId:%d:type:%d", EcpmRankingsListPrefix, appId, t)
}

// SetList 向排行榜中添加成员及其分数
func (r *Ranking) SetList(ctx context.Context, key string, data ...redis.Z) {
	r.c.ZAdd(ctx, key, data...)
}

// GetList 获取排行榜,按照分数从高到低排序
func (r *Ranking) GetList(ctx context.Context, key string, start, stop int64) (data []redis.Z, err error) {
	return r.c.ZRevRangeWithScores(ctx, key, start, stop).Result()
}

// GetRank 获取指定成员在排行榜中的排名(排名从 0 开始,分数越高排名越靠前)
func (r *Ranking) GetRank(ctx context.Context, key, member string) (rank int64, err error) {
	return r.c.ZRevRank(ctx, key, member).Result()
}

// GetRankAndScore 获取指定成员在排行榜中的排名(排名从 0 开始,分数越高排名越靠前)
func (r *Ranking) GetRankAndScore(ctx context.Context, key, member string) (rank int64, score float64, err error) {
	rankScore := r.c.ZRevRankWithScore(ctx, key, member).Val()
	return rankScore.Rank, rankScore.Score, nil
}

// GetScore 获取指定成员在排行榜中的分数
func (r *Ranking) GetScore(ctx context.Context, key, member string) (score float64, err error) {
	return r.c.ZScore(ctx, key, member).Result()
}

// IncrScore 增加分数
func (r *Ranking) IncrScore(ctx context.Context, key, member string, increment float64) (score float64, err error) {
	return r.c.ZIncrBy(ctx, key, increment, member).Result()
}

// Remove 删除排行榜
func (r *Ranking) Remove(ctx context.Context, key string) (err error) {
	return r.c.Del(ctx, key).Err()
}

// 使用 Lua 保证原子性操作
var getHigherByScoreScript = redis.NewScript(`
local members = redis.call('ZRANGEBYSCORE', KEYS[1], ARGV[1],'+inf','LIMIT', 0, 1, 'WITHSCORES')
if #members == 0 then return nil end
return members
`)

// AtomicGetHigherUser 获取排行榜的前一名
func (r *Ranking) AtomicGetHigherUser(ctx context.Context, key string, score float64) (userId uint64, scoreRes uint32, err error) {
	s, err := getHigherByScoreScript.Run(ctx, r.c, []string{key}, score).Slice()
	if err != nil {
		return
	}
	if s == nil {
		return
	}

	if len(s) != 2 {
		return 0, 0, fmt.Errorf("redis type error len(s) !=2")
	}

	userId, err = strconv.ParseUint(s[0].(string), 10, 64)
	if err != nil {
		return
	}
	tmp, err := strconv.ParseUint(s[1].(string), 10, 64)
	if err != nil {
		return
	}
	return userId, uint32(tmp >> 32), nil
}