package db

import (
	"github.com/garyburd/redigo/redis"
	"time"
)

// RedisClient 封装了Redis连接池和操作方法
type RedisClient struct {
	pool *redis.Pool
}

// NewRedisClient 创建一个新的Redis客户端
// 参数:
//
//	addr: Redis服务器地址，格式为"host:port"
//	password: Redis认证密码，如果无密码则为空字符串
//	db: 选择的数据库索引
//
// 返回值:
//
//	返回一个指向RedisClient的指针
func NewRedisClient(addr, password string, db int) *RedisClient {
	return &RedisClient{
		pool: &redis.Pool{
			MaxIdle:     30,               // 最大空闲连接数
			MaxActive:   1000,             // 最大活跃连接数
			IdleTimeout: 60 * time.Second, // 空闲连接超时时间
			Dial: func() (redis.Conn, error) {
				// 建立连接
				c, err := redis.Dial("tcp", addr,
					redis.DialPassword(password),
					redis.DialDatabase(db),
				)
				if err != nil {
					return nil, err
				}
				return c, nil
			},
		},
	}
}

// exec 执行一个Redis命令
// 参数:
//
//	cmd: 命令字符串
//	args: 命令参数
//
// 返回值:
//
//	interface{}: Redis返回的结果
//	error: 执行命令过程中出现的错误，如果没有错误则为nil
func (r *RedisClient) exec(cmd string, args ...interface{}) (interface{}, error) {
	conn := r.pool.Get()
	defer conn.Close()
	return conn.Do(cmd, args...)
}

// Exists 检查一个或多个键是否存在
// 参数:
//
//	key: 键名
//
// 返回值:
//
//	bool: 如果键存在则为true，否则为false
//	error: 执行命令过程中出现的错误，如果没有错误则为nil
func (r *RedisClient) Exists(key string) (bool, error) {
	res, err := redis.Int(r.exec("EXISTS", key))
	return res > 0, err
}

// Expire 设置键的过期时间（单位为秒）
// 参数:
//
//	key: 键名
//	sec: 过期时间（秒）
//
// 返回值:
//
//	error: 执行命令过程中出现的错误，如果没有错误则为nil
func (r *RedisClient) Expire(key string, sec int) error {
	_, err := redis.Int(r.exec("EXPIRE", key, sec))
	return err
}

// Del 删除一个或多个键
// 参数:
//
//	keys: 要删除的键名列表
//
// 返回值:
//
//	int: 成功删除的键的数量
//	error: 执行命令过程中出现的错误，如果没有错误则为nil
func (r *RedisClient) Del(keys ...string) (int, error) {
	args := redis.Args{}.AddFlat(keys)
	return redis.Int(r.exec("DEL", args...))
}

// Set 设置键的字符串值
// 参数:
//
//	key: 键名
//	value: 值，可以是任意类型，但最终会被转换为字符串存储
//
// 返回值:
//
//	error: 执行命令过程中出现的错误，如果没有错误则为nil
func (r *RedisClient) Set(key string, value interface{}) error {
	_, err := r.exec("SET", key, value)
	return err
}

// Get 获取键的字符串值
// 参数:
//
//	key: 键名
//
// 返回值:
//
//	string: 键对应的字符串值
//	error: 执行命令过程中出现的错误，如果没有错误则为nil；如果键不存在，返回redis.ErrNil
func (r *RedisClient) Get(key string) (string, error) {
	return redis.String(r.exec("GET", key))
}

// Incr 将键中储存的数字值增一
// 参数:
//
//	key: 键名
//
// 返回值:
//
//	int: 递增后的值
//	error: 执行命令过程中出现的错误，如果没有错误则为nil
func (r *RedisClient) Incr(key string) (int, error) {
	return redis.Int(r.exec("INCR", key))
}

// MGet 获取多个键的字符串值
// 参数:
//
//	keys: 键名列表
//
// 返回值:
//
//	[]string: 每个键对应的字符串值，如果某个键不存在，则对应位置的值为空字符串
//	error: 执行命令过程中出现的错误，如果没有错误则为nil
func (r *RedisClient) MGet(keys ...string) ([]string, error) {
	args := redis.Args{}.AddFlat(keys)
	return redis.Strings(r.exec("MGET", args...))
}

// HSet 设置哈希表中字段的值
// 参数:
//
//	key: 哈希表键名
//	field: 字段名
//	value: 字段值
//
// 返回值:
//
//	error: 执行命令过程中出现的错误，如果没有错误则为nil
func (r *RedisClient) HSet(key, field string, value interface{}) error {
	_, err := r.exec("HSET", key, field, value)
	return err
}

// HGet 获取哈希表中字段的值
// 参数:
//
//	key: 哈希表键名
//	field: 字段名
//
// 返回值:
//
//	string: 字段对应的字符串值
//	error: 执行命令过程中出现的错误，如果没有错误则为nil；如果字段不存在，返回redis.ErrNil
func (r *RedisClient) HGet(key, field string) (string, error) {
	return redis.String(r.exec("HGET", key, field))
}

// HGetAll 获取哈希表中所有的字段和值
// 参数:
//
//	key: 哈希表键名
//
// 返回值:
//
//	map[string]string: 字段和值的映射
//	error: 执行命令过程中出现的错误，如果没有错误则为nil
func (r *RedisClient) HGetAll(key string) (map[string]string, error) {
	return redis.StringMap(r.exec("HGETALL", key))
}

// LPush 将一个或多个值插入到列表头部
// 参数:
//
//	key: 列表键名
//	values: 要插入的值列表
//
// 返回值:
//
//	int: 执行命令后列表的长度
//	error: 执行命令过程中出现的错误，如果没有错误则为nil
func (r *RedisClient) LPush(key string, values ...interface{}) (int, error) {
	args := redis.Args{key}.AddFlat(values)
	return redis.Int(r.exec("LPUSH", args...))
}

// LRange 获取列表中指定范围内的元素
// 参数:
//
//	key: 列表键名
//	start: 起始索引（包含）
//	end: 结束索引（包含），-1表示最后一个元素
//
// 返回值:
//
//	[]string: 指定范围内的元素列表
//	error: 执行命令过程中出现的错误，如果没有错误则为nil
func (r *RedisClient) LRange(key string, start, end int) ([]string, error) {
	return redis.Strings(r.exec("LRANGE", key, start, end))
}

// SAdd 向集合添加一个或多个成员
// 参数:
//
//	key: 集合键名
//	members: 要添加的成员列表
//
// 返回值:
//
//	int: 成功添加到集合的新元素的数量，不包括已经存在于集合中的元素
//	error: 执行命令过程中出现的错误，如果没有错误则为nil
func (r *RedisClient) SAdd(key string, members ...interface{}) (int, error) {
	args := redis.Args{key}.AddFlat(members)
	return redis.Int(r.exec("SADD", args...))
}

// SInter 返回给定所有集合的交集
// 参数:
//
//	keys: 集合键名列表
//
// 返回值:
//
//	[]string: 交集成员的列表
//	error: 执行命令过程中出现的错误，如果没有错误则为nil
func (r *RedisClient) SInter(keys ...string) ([]string, error) {
	args := redis.Args{}.AddFlat(keys)
	return redis.Strings(r.exec("SINTER", args...))
}

// ZAdd 向有序集合添加一个或多个成员，或者更新已存在成员的分数
// 参数:
//
//	key: 有序集合键名
//	score: 成员的分数
//	member: 成员
//
// 返回值:
//
//	int: 成功添加的新成员的数量（不包括更新的）
//	error: 执行命令过程中出现的错误，如果没有错误则为nil
func (r *RedisClient) ZAdd(key string, score float64, member interface{}) (int, error) {
	return redis.Int(r.exec("ZADD", key, score, member))
}

// ZRevRange 返回有序集中指定区间内的成员，通过索引，分数从高到低
// 参数:
//
//	key: 有序集合键名
//	start: 起始索引
//	stop: 结束索引
//
// 返回值:
//
//	[]string: 指定区间内，分数从高到低排列的成员列表
//	error: 执行命令过程中出现的错误，如果没有错误则为nil
func (r *RedisClient) ZRevRange(key string, start, stop int) ([]string, error) {
	return redis.Strings(r.exec("ZREVRANGE", key, start, stop))
}

// Pipeline 执行流水线操作，一次性发送多个命令并接收多个响应
// 参数:
//
//	cmds: 命令结构体列表，每个结构体包含命令字符串和参数列表
//
// 返回值:
//
//	[]interface{}: 每个命令的执行结果
//	error: 执行过程中出现的错误，如果没有错误则为nil
func (r *RedisClient) Pipeline(cmds []struct {
	Cmd  string
	Args []interface{}
}) ([]interface{}, error) {
	conn := r.pool.Get()
	defer conn.Close()

	for _, c := range cmds {
		if err := conn.Send(c.Cmd, c.Args...); err != nil {
			return nil, err
		}
	}
	conn.Flush()

	results := make([]interface{}, len(cmds))
	for i := range cmds {
		res, err := conn.Receive()
		if err != nil {
			return nil, err
		}
		results[i] = res
	}
	return results, nil
}

// Subscribe 订阅给定的频道，并在接收到消息时调用处理函数
// 参数:
//
//	channels: 要订阅的频道列表
//	handler: 消息处理函数，参数为频道和消息内容
//
// 返回值:
//
//	error: 执行过程中出现的错误，如果没有错误则为nil
func (r *RedisClient) Subscribe(channels []string, handler func(channel, msg string)) error {
	conn := r.pool.Get()
	defer conn.Close()

	psc := redis.PubSubConn{Conn: conn}
	psc.Subscribe(redis.Args{}.AddFlat(channels)...)

	for {
		switch v := psc.Receive().(type) {
		case redis.Message:
			handler(v.Channel, string(v.Data))
		case error:
			return v
		}
	}
}
