package com.njzscloud.common.redis.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.njzscloud.common.redis.RedisCli;
import io.lettuce.core.*;
import io.lettuce.core.api.sync.RedisCommands;
import io.lettuce.core.output.KeyStreamingChannel;
import io.lettuce.core.output.KeyValueStreamingChannel;
import io.lettuce.core.output.ScoredValueStreamingChannel;
import io.lettuce.core.output.ValueStreamingChannel;
import lombok.extern.slf4j.Slf4j;

import java.time.Duration;
import java.time.Instant;
import java.util.*;

/**
 * Redis 工具
 */
@Slf4j
@SuppressWarnings("unchecked")
public final class Redis {

    private final static RedisCli REDIS_CLI;

    static {
        REDIS_CLI = SpringUtil.getBean(RedisCli.class);
    }

    // region db/key

    /**
     * 切换数据库
     *
     * @param db 数据库索引
     * @return boolean
     * @see RedisCommands#select(int)
     */
    public static boolean select(int db) {
        return REDIS_CLI.exec(it -> {
            return "OK".equalsIgnoreCase(it.select(db));
        });
    }

    /**
     * 向指定通道发送消息
     *
     * @param channel 通道
     * @param message 消息
     * @return Long 接收消息的客户端数
     */
    public static Long publish(String channel, Object message) {
        return REDIS_CLI.exec(it -> {
            return it.publish(channel, message);
        });
    }

    public static List<String> keys(String pattern) {
        return REDIS_CLI.exec(it -> {
            return it.keys(pattern);
        });
    }

    public static String rename(String key, String newKey) {
        return REDIS_CLI.exec(it -> {
            return it.rename(key, newKey);
        });
    }

    public static Boolean renamenx(String key, String newKey) {
        return REDIS_CLI.exec(it -> {
            return it.renamenx(key, newKey);
        });
    }

    public static Long del(String... keys) {
        return REDIS_CLI.exec(it -> {
            return it.del(keys);
        });
    }

    public static Long exists(String... keys) {
        return REDIS_CLI.exec(it -> {
            return it.exists(keys);
        });
    }

    public static Boolean expire(String key, long seconds) {
        return REDIS_CLI.exec(it -> {
            return it.expire(key, seconds);
        });
    }

    public static Boolean expire(String key, Duration seconds) {
        return REDIS_CLI.exec(it -> {
            return it.expire(key, seconds);
        });
    }

    public static Boolean expireat(String key, long timestamp) {
        return REDIS_CLI.exec(it -> {
            return it.expireat(key, timestamp);
        });
    }

    public static Boolean expireat(String key, Date timestamp) {
        return REDIS_CLI.exec(it -> {
            return it.expireat(key, timestamp);
        });
    }

    public static Boolean expireat(String key, Instant timestamp) {
        return REDIS_CLI.exec(it -> {
            return it.expireat(key, timestamp);
        });
    }
    // endregion

    // region str

    public static <V> V get(String key) {
        return REDIS_CLI.exec(it -> (V) it.get(key));
    }

    public static String set(String key, Object value, SetArgs setArgs) {
        return REDIS_CLI.exec(it -> {
            return it.set(key, value, setArgs);
        });
    }

    public static String set(String key, Object value) {
        return REDIS_CLI.exec(it -> {
            return it.set(key, value);
        });
    }

    public static String setex(String key, long seconds, Object value) {
        return REDIS_CLI.exec(it -> {
            return it.setex(key, seconds, value);
        });
    }

    public static Boolean setnx(String key, Object value) {
        return REDIS_CLI.exec(it -> {
            return it.setnx(key, value);
        });
    }

    public static List<KeyValue<String, Object>> mget(String... keys) {
        return REDIS_CLI.exec(it -> {
            return it.mget(keys);
        });
    }

    public static Long mget(KeyValueStreamingChannel<String, Object> channel, String... keys) {
        return REDIS_CLI.exec(it -> {
            return it.mget(channel, keys);
        });
    }

    public static String mset(Map<String, Object> map) {
        return REDIS_CLI.exec(it -> {
            return it.mset(map);
        });
    }

    public static Boolean msetnx(Map<String, Object> map) {
        return REDIS_CLI.exec(it -> {
            return it.msetnx(map);
        });
    }

    public static Long incr(String key) {
        return REDIS_CLI.exec(it -> {
            return it.incr(key);
        });
    }

    public static Long incrby(String key, long amount) {
        return REDIS_CLI.exec(it -> {
            return it.incrby(key, amount);
        });
    }

    public static Long decr(String key) {
        return REDIS_CLI.exec(it -> {
            return it.decr(key);
        });
    }

    public static Long decrby(String key, long amount) {
        return REDIS_CLI.exec(it -> {
            return it.decrby(key, amount);
        });
    }

    public static Long strlen(String key) {
        return REDIS_CLI.exec(it -> {
            return it.strlen(key);
        });
    }


    public static Long bitcount(String key) {
        return REDIS_CLI.exec(it -> {
            return it.bitcount(key);
        });
    }

    public static Long bitcount(String key, long start, long end) {
        return REDIS_CLI.exec(it -> {
            return it.bitcount(key, start, end);
        });
    }

    public static List<Long> bitfield(String key, BitFieldArgs bitFieldArgs) {
        return REDIS_CLI.exec(it -> {
            return it.bitfield(key, bitFieldArgs);
        });
    }

    public static Long bitpos(String key, boolean state) {
        return REDIS_CLI.exec(it -> {
            return it.bitpos(key, state);
        });
    }

    public static Long bitpos(String key, boolean state, long start) {
        return REDIS_CLI.exec(it -> {
            return it.bitpos(key, state, start);
        });
    }

    public static Long bitpos(String key, boolean state, long start, long end) {
        return REDIS_CLI.exec(it -> {
            return it.bitpos(key, state, start, end);
        });
    }

    public static Long bitopAnd(String destination, String... keys) {
        return REDIS_CLI.exec(it -> {
            return it.bitopAnd(destination, keys);
        });
    }

    public static Long bitopNot(String destination, String source) {
        return REDIS_CLI.exec(it -> {
            return it.bitopNot(destination, source);
        });
    }

    public static Long bitopOr(String destination, String... keys) {
        return REDIS_CLI.exec(it -> {
            return it.bitopOr(destination, keys);
        });
    }

    public static Long bitopXor(String destination, String... keys) {
        return REDIS_CLI.exec(it -> {
            return it.bitopXor(destination, keys);
        });
    }
    // endregion

    // region hash
    public static Long hdel(String key, String... fields) {
        return REDIS_CLI.exec(it -> {
            return it.hdel(key, fields);
        });
    }

    public static Boolean hexists(String key, String field) {
        return REDIS_CLI.exec(it -> {
            return it.hexists(key, field);
        });
    }

    public static <V> V hget(String key, String field) {
        return REDIS_CLI.exec(it -> (V) it.hget(key, field));
    }

    public static Map<String, Object> hgetall(String key) {
        return REDIS_CLI.exec(it -> {
            return it.hgetall(key);
        });
    }

    public static Long hgetall(KeyValueStreamingChannel<String, Object> channel, String key) {
        return REDIS_CLI.exec(it -> {
            return it.hgetall(channel, key);
        });
    }

    public static List<String> hkeys(String key) {
        return REDIS_CLI.exec(it -> {
            return it.hkeys(key);
        });
    }

    public static Long hkeys(KeyStreamingChannel<String> channel, String key) {
        return REDIS_CLI.exec(it -> {
            return it.hkeys(channel, key);
        });
    }

    public static Long hlen(String key) {
        return REDIS_CLI.exec(it -> {
            return it.hlen(key);
        });
    }

    public static List<KeyValue<String, Object>> hmget(String key, String... fields) {
        return REDIS_CLI.exec(it -> {
            return it.hmget(key, fields);
        });
    }

    public static Long hmget(KeyValueStreamingChannel<String, Object> channel, String key, String... fields) {
        return REDIS_CLI.exec(it -> {
            return it.hmget(channel, key, fields);
        });
    }

    public static String hmset(String key, Map<String, Object> map) {
        return REDIS_CLI.exec(it -> {
            return it.hmset(key, map);
        });
    }

    public static Boolean hset(String key, String field, Object value) {
        return REDIS_CLI.exec(it -> {
            return it.hset(key, field, value);
        });
    }

    public static Long hset(String key, Map<String, Object> map) {
        return REDIS_CLI.exec(it -> {
            return it.hset(key, map);
        });
    }

    public static Boolean hsetnx(String key, String field, Object value) {
        return REDIS_CLI.exec(it -> {
            return it.hsetnx(key, field, value);
        });
    }

    public static Long hstrlen(String key, String field) {
        return REDIS_CLI.exec(it -> {
            return it.hstrlen(key, field);
        });
    }

    public static List<Object> hvals(String key) {
        return REDIS_CLI.exec(it -> {
            return it.hvals(key);
        });
    }

    public static Long hvals(ValueStreamingChannel<Object> channel, String key) {
        return REDIS_CLI.exec(it -> {
            return it.hvals(channel, key);
        });
    }
    // endregion

    // region list
    public static <V> V lindex(String key, long index) {
        return REDIS_CLI.exec(it -> (V) it.lindex(key, index));
    }


    public static Long llen(String key) {
        return REDIS_CLI.exec(it -> {
            return it.llen(key);
        });
    }

    public static <V> V lpop(String key) {
        return REDIS_CLI.exec(it -> (V) it.lpop(key));
    }

    public static <V> List<V> lpop(String key, long count) {
        return REDIS_CLI.exec(it -> (List<V>) it.lpop(key, count));
    }

    public static <V> Long lpush(String key, V... values) {
        return REDIS_CLI.exec(it -> {
            return it.lpush(key, values);
        });
    }

    public static <V> Long lpushx(String key, V... values) {
        return REDIS_CLI.exec(it -> {
            return it.lpushx(key, values);
        });
    }

    public static <V> List<V> lrange(String key, long start, long stop) {
        return REDIS_CLI.exec(it -> (List<V>) it.lrange(key, start, stop));
    }

    public static <V> Long lrange(ValueStreamingChannel<V> channel, String key, long start, long stop) {
        return REDIS_CLI.exec(it -> {
            return it.lrange((ValueStreamingChannel<Object>) channel, key, start, stop);
        });
    }

    public static <V> Long lrem(String key, long count, V value) {
        return REDIS_CLI.exec(it -> {
            return it.lrem(key, count, value);
        });
    }

    public static <V> String lset(String key, long index, V value) {
        return REDIS_CLI.exec(it -> {
            return it.lset(key, index, value);
        });
    }

    public static String ltrim(String key, long start, long stop) {
        return REDIS_CLI.exec(it -> {
            return it.ltrim(key, start, stop);
        });
    }

    public static <V> V rpop(String key) {
        return REDIS_CLI.exec(it -> (V) it.rpop(key));
    }

    public static <V> List<V> rpop(String key, long count) {
        return REDIS_CLI.exec(it -> (List<V>) it.rpop(key, count));
    }

    public static <V> Long rpush(String key, V... values) {
        return REDIS_CLI.exec(it -> {
            return it.rpush(key, values);
        });
    }

    public static <V> Long rpushx(String key, V... values) {
        return REDIS_CLI.exec(it -> {
            return it.rpushx(key, values);
        });
    }
    // endregion

    // region set
    public static <V> Long sadd(String key, V... members) {
        return REDIS_CLI.exec(it -> {
            return it.sadd(key, members);
        });
    }

    public static Long scard(String key) {
        return REDIS_CLI.exec(it -> {
            return it.scard(key);
        });
    }

    public static <V> Set<V> sdiff(String... keys) {
        return REDIS_CLI.exec(it -> (Set<V>) it.sdiff(keys));
    }

    public static <V> Long sdiff(ValueStreamingChannel<V> channel, String... keys) {
        return REDIS_CLI.exec(it -> {
            return it.sdiff((ValueStreamingChannel<Object>) channel, keys);
        });
    }

    public static Long sdiffstore(String destination, String... keys) {
        return REDIS_CLI.exec(it -> {
            return it.sdiffstore(destination, keys);
        });
    }

    public static <V> Set<V> sinter(String... keys) {
        return REDIS_CLI.exec(it -> (Set<V>) it.sinter(keys));
    }

    public static <V> Long sinter(ValueStreamingChannel<V> channel, String... keys) {
        return REDIS_CLI.exec(it -> {
            return it.sinter((ValueStreamingChannel<Object>) channel, keys);
        });
    }

    public static Long sinterstore(String destination, String... keys) {
        return REDIS_CLI.exec(it -> {
            return it.sinterstore(destination, keys);
        });
    }

    public static <V> Boolean sismember(String key, V member) {
        return REDIS_CLI.exec(it -> {
            return it.sismember(key, member);
        });
    }

    public static <V> List<Boolean> smismember(String key, V... member) {
        return REDIS_CLI.exec(it -> {
            return it.smismember(key, member);
        });
    }

    public static <V> Set<V> smembers(String key) {
        return REDIS_CLI.exec(it -> (Set<V>) it.smembers(key));
    }

    public static <V> Long smembers(ValueStreamingChannel<V> channel, String key) {
        return REDIS_CLI.exec(it -> {
            return it.smembers((ValueStreamingChannel<Object>) channel, key);
        });
    }

    public static <V> V spop(String key) {
        return REDIS_CLI.exec(it -> (V) it.spop(key));
    }

    public static <V> Set<V> spop(String key, long count) {
        return REDIS_CLI.exec(it -> (Set<V>) it.spop(key, count));
    }

    public static <V> V srandmember(String key) {
        return REDIS_CLI.exec(it -> (V) it.srandmember(key));
    }

    public static <V> List<V> srandmember(String key, long count) {
        return REDIS_CLI.exec(it -> (List<V>) it.srandmember(key, count));
    }

    public static <V> Long srandmember(ValueStreamingChannel<V> channel, String key, long count) {
        return REDIS_CLI.exec(it -> {
            return it.srandmember((ValueStreamingChannel<Object>) channel, key, count);
        });
    }

    public static <V> Long srem(String key, V... members) {
        return REDIS_CLI.exec(it -> {
            return it.srem(key, members);
        });
    }

    public static <V> Set<V> sunion(String... keys) {
        return REDIS_CLI.exec(it -> (Set<V>) it.sunion(keys));
    }

    public static <V> Long sunion(ValueStreamingChannel<V> channel, String... keys) {
        return REDIS_CLI.exec(it -> {
            return it.sunion((ValueStreamingChannel<Object>) channel, keys);
        });
    }

    public static Long sunionstore(String destination, String... keys) {
        return REDIS_CLI.exec(it -> {
            return it.sunionstore(destination, keys);
        });
    }

    // endregion

    // region sorted set
    public static <V> Long zadd(String key, double score, V member) {
        return REDIS_CLI.exec(it -> {
            return it.zadd(key, score, member);
        });
    }

    public static Long zadd(String key, Object... scoresAndValues) {
        return REDIS_CLI.exec(it -> {
            return it.zadd(key, scoresAndValues);
        });
    }

    public static <V> Long zadd(String key, ScoredValue<V>... scoredValues) {
        return REDIS_CLI.exec(it -> {
            return it.zadd(key, (ScoredValue<Object>[]) scoredValues);
        });
    }

    public static <V> Long zadd(String key, ZAddArgs zAddArgs, double score, V member) {
        return REDIS_CLI.exec(it -> {
            return it.zadd(key, zAddArgs, score, member);
        });
    }

    public static Long zadd(String key, ZAddArgs zAddArgs, Object... scoresAndValues) {
        return REDIS_CLI.exec(it -> {
            return it.zadd(key, zAddArgs, scoresAndValues);
        });
    }

    public static <V> Long zadd(String key, ZAddArgs zAddArgs, ScoredValue<V>... scoredValues) {
        return REDIS_CLI.exec(it -> {
            return it.zadd(key, zAddArgs, (ScoredValue<Object>[]) scoredValues);
        });
    }

    public static Long zcard(String key) {
        return REDIS_CLI.exec(it -> {
            return it.zcard(key);
        });
    }

    public static Long zcount(String key, Range<? extends Number> range) {
        return REDIS_CLI.exec(it -> {
            return it.zcount(key, range);
        });
    }

    public static <V> List<V> zdiff(String... keys) {
        return REDIS_CLI.exec(it -> (List<V>) it.zdiff(keys));
    }

    public static Long zdiffstore(String destKey, String... srcKeys) {
        return REDIS_CLI.exec(it -> {
            return it.zdiffstore(destKey, srcKeys);
        });
    }

    public static <V> List<V> zinter(String... keys) {
        return REDIS_CLI.exec(it -> (List<V>) it.zinter(keys));
    }

    public static <V> List<V> zinter(ZAggregateArgs aggregateArgs, String... keys) {
        return REDIS_CLI.exec(it -> (List<V>) it.zinter(aggregateArgs, keys));
    }

    public static Long zinterstore(String destination, String... keys) {
        return REDIS_CLI.exec(it -> {
            return it.zinterstore(destination, keys);
        });
    }

    public static Long zinterstore(String destination, ZStoreArgs storeArgs, String... keys) {
        return REDIS_CLI.exec(it -> {
            return it.zinterstore(destination, storeArgs, keys);
        });
    }

    public static <V> V zrandmember(String key) {
        return REDIS_CLI.exec(it -> (V) it.zrandmember(key));
    }

    public static <V> List<V> zrandmember(String key, long count) {
        return REDIS_CLI.exec(it -> (List<V>) it.zrandmember(key, count));
    }

    public static <V> ScoredValue<V> zrandmemberWithScores(String key) {
        return REDIS_CLI.exec(it -> (ScoredValue<V>) it.zrandmemberWithScores(key));
    }

    public static <V> List<ScoredValue<V>> zrandmemberWithScores(String key, long count) {
        List<ScoredValue<Object>> scoredValues = REDIS_CLI.exec(it -> it.zrandmemberWithScores(key, count));

        if (scoredValues == null || scoredValues.isEmpty()) return CollUtil.empty(null);

        ArrayList<ScoredValue<V>> list = new ArrayList<>(scoredValues.size());
        for (ScoredValue<Object> scoredValue : scoredValues) {
            double score = scoredValue.getScore();
            Object value = scoredValue.getValue();
            ScoredValue<V> val = (ScoredValue<V>) ScoredValue.fromNullable(score, (V) value);
            list.add(val);
        }

        return list;
    }

    public static <V> List<V> zrange(String key, long start, long stop) {
        return REDIS_CLI.exec(it -> (List<V>) it.zrange(key, start, stop));
    }

    public static <V> Long zrange(ValueStreamingChannel<V> channel, String key, long start, long stop) {
        return REDIS_CLI.exec(it -> {
            return it.zrange((ValueStreamingChannel<Object>) channel, key, start, stop);
        });
    }

    public static <V> List<ScoredValue<V>> zrangeWithScores(String key, long start, long stop) {
        List<ScoredValue<Object>> scoredValues = REDIS_CLI.exec(it -> it.zrangeWithScores(key, start, stop));
        if (scoredValues == null || scoredValues.isEmpty()) return CollUtil.empty(null);

        ArrayList<ScoredValue<V>> list = new ArrayList<>(scoredValues.size());
        for (ScoredValue<Object> scoredValue : scoredValues) {
            double score = scoredValue.getScore();
            Object value = scoredValue.getValue();
            ScoredValue<V> val = (ScoredValue<V>) ScoredValue.fromNullable(score, (V) value);
            list.add(val);
        }

        return list;
    }

    public static <V> Long zrangeWithScores(ScoredValueStreamingChannel<V> channel, String key, long start, long stop) {
        return REDIS_CLI.exec(it -> {
            return it.zrangeWithScores((ScoredValueStreamingChannel<Object>) channel, key, start, stop);
        });
    }

    public static <V> List<V> zrevrange(String key, long start, long stop) {
        return REDIS_CLI.exec(it -> (List<V>) it.zrevrange(key, start, stop));
    }

    public static <V> Long zrevrange(ValueStreamingChannel<V> channel, String key, long start, long stop) {
        return REDIS_CLI.exec(it -> {
            return it.zrevrange((ValueStreamingChannel<Object>) channel, key, start, stop);
        });
    }

    public static <V> List<V> zunion(String... keys) {
        return REDIS_CLI.exec(it -> (List<V>) it.zunion(keys));
    }

    public static <V> List<V> zunion(ZAggregateArgs aggregateArgs, String... keys) {
        return REDIS_CLI.exec(it -> (List<V>) it.zunion(aggregateArgs, keys));
    }

    public static <V> List<ScoredValue<V>> zunionWithScores(ZAggregateArgs aggregateArgs, String... keys) {
        List<ScoredValue<Object>> scoredValues = REDIS_CLI.exec(it -> it.zunionWithScores(aggregateArgs, keys));
        if (scoredValues == null || scoredValues.isEmpty()) return CollUtil.empty(null);

        ArrayList<ScoredValue<V>> list = new ArrayList<>(scoredValues.size());
        for (ScoredValue<Object> scoredValue : scoredValues) {
            double score = scoredValue.getScore();
            Object value = scoredValue.getValue();
            ScoredValue<V> val = (ScoredValue<V>) ScoredValue.fromNullable(score, (V) value);
            list.add(val);
        }
        return list;
    }

    public static <V> List<ScoredValue<V>> zunionWithScores(String... keys) {
        List<ScoredValue<Object>> scoredValues = REDIS_CLI.exec(it -> it.zunionWithScores(keys));
        if (scoredValues == null || scoredValues.isEmpty()) return CollUtil.empty(null);

        ArrayList<ScoredValue<V>> list = new ArrayList<>(scoredValues.size());
        for (ScoredValue<Object> scoredValue : scoredValues) {
            double score = scoredValue.getScore();
            Object value = scoredValue.getValue();
            ScoredValue<V> val = (ScoredValue<V>) ScoredValue.fromNullable(score, (V) value);
            list.add(val);
        }
        return list;
    }

    public static Long zunionstore(String destination, String... keys) {
        return REDIS_CLI.exec(it -> {
            return it.zunionstore(destination, keys);
        });
    }

    public static Long zunionstore(String destination, ZStoreArgs storeArgs, String... keys) {
        return REDIS_CLI.exec(it -> {
            return it.zunionstore(destination, storeArgs, keys);
        });
    }
    // endregion
}

