package com.njzscloud.common.redis;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import com.njzscloud.common.core.ex.Exceptions;
import com.njzscloud.common.core.fastjson.Fastjson;
import com.njzscloud.common.redis.support.RedisFastjsonCodec;
import com.njzscloud.common.redis.support.RedisMessageDispatch;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.TransactionResult;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
import io.lettuce.core.codec.RedisCodec;
import io.lettuce.core.pubsub.RedisPubSubListener;
import io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
import io.lettuce.core.support.ConnectionPoolSupport;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * Redis 客户端，对 lettuce 客户端的封装
 */
@Slf4j
@SuppressWarnings("unchecked")
public class RedisCli {

    private final RedisCodec<String, Object> codec = new RedisFastjsonCodec();

    /**
     * lettuce 客户端
     */
    @Getter
    private final RedisClient redisClient;

    /**
     * 普通连接池
     */
    private final GenericObjectPool<StatefulRedisConnection<String, Object>> pool;

    /**
     * 发布订阅模式连接池
     */
    private final GenericObjectPool<StatefulRedisPubSubConnection<String, Object>> pubSubPool;

    /**
     * 已订阅的频道模式
     */
    private final Set<String> subscribedPatterns = new HashSet<>();
    /**
     * 已订阅的频道
     */
    private final Set<String> subscribedChannels = new HashSet<>();

    /**
     * 创建 Redis 客户端
     *
     * @param uri        Redis 连接 URI
     * @param poolConfig 连接池配置
     * @param pubsub     是否开启发布订阅模式支持
     */
    public RedisCli(RedisURI uri, GenericObjectPoolConfig<?> poolConfig, boolean pubsub) {
        redisClient = RedisClient.create(uri);

        pool = ConnectionPoolSupport.createGenericObjectPool(() -> redisClient.connect(codec), (GenericObjectPoolConfig<StatefulRedisConnection<String, Object>>) poolConfig);
        if (pubsub) pubSubPool = ConnectionPoolSupport.createGenericObjectPool(() -> redisClient.connectPubSub(codec), (GenericObjectPoolConfig<StatefulRedisPubSubConnection<String, Object>>) poolConfig);
        else pubSubPool = null;
    }

    /**
     * 退出
     */
    public void exit() {
        if (pubSubPool != null) {
            punsubscribe(subscribedPatterns);
            unsubscribe(subscribedChannels);
            pubSubPool.close();
        }
        pool.close();
        redisClient.shutdown();
    }

    /**
     * 执行 Redis 命令
     *
     * @param fn  要执行的操作
     * @param <T> Redis 值类型
     * @param <R> 返回值类型
     * @return 返回 fn 的返回值
     */
    public <T, R> R exec(Function<RedisCommands<String, T>, R> fn) {
        try (StatefulRedisConnection<String, Object> connection = pool.borrowObject()) {
            return fn.apply((RedisCommands<String, T>) connection.sync());
        } catch (Exception e) {
            throw Exceptions.error(e, "Redis 操作执行失败");
        }
    }

    /**
     * 执行 Redis 命令（带事务）
     *
     * @param fn  要执行的操作
     * @param <T> Redis 值类型
     * @return {@link TransactionResult} 事务结果
     */
    public <T> TransactionResult execWithTransaction(Consumer<RedisCommands<String, T>> fn) {
        try (StatefulRedisConnection<String, Object> connection = pool.borrowObject()) {
            RedisCommands<String, T> commands = (RedisCommands<String, T>) connection.sync();
            commands.multi();
            fn.accept(commands);
            return commands.exec();
        } catch (Exception e) {
            throw Exceptions.error(e, "Redis 操作执行失败");
        }
    }

    /**
     * 添加监听器
     *
     * @see RedisMessageDispatch
     */
    public void addListener(RedisPubSubListener<String, Object> redisPubSubListener) {
        try (StatefulRedisPubSubConnection<String, Object> connection = pubSubPool.borrowObject()) {
            connection.addListener(redisPubSubListener);
        } catch (Exception e) {
            throw Exceptions.error(e, "Redis 监听器注册失败");
        }
    }

    /**
     * 发布消息
     *
     * @param channel 频道
     * @param message 消息内容
     */
    public void publish(String channel, Object message) {
        if (log.isDebugEnabled()) {
            log.debug("发布消息，频道：【{}】，消息：【{}】", channel, Fastjson.toJsonStr(message));
        }
        try (StatefulRedisPubSubConnection<String, Object> connection = pubSubPool.borrowObject()) {
            connection.sync().publish(channel, message);
        } catch (Exception e) {
            throw Exceptions.error(e, "Redis 消息发布失败，频道：【{}】，消息：【{}】", channel, Fastjson.toJsonStr(message));
        }
    }

    /**
     * 订阅
     *
     * @param channels 频道
     */
    public void subscribe(Collection<String> channels) {
        if (log.isDebugEnabled()) {
            log.debug("订阅，频道：【{}】", channels);
        }
        Collection<String> newChannels = CollUtil.subtract(CollUtil.distinct(channels), subscribedChannels);
        try (StatefulRedisPubSubConnection<String, Object> connection = pubSubPool.borrowObject()) {
            if (newChannels.isEmpty()) return;
            connection.sync().subscribe(ArrayUtil.toArray(newChannels, String.class));
            subscribedChannels.addAll(newChannels);
            if (log.isDebugEnabled()) {
                log.debug("当前已订阅的频道：【{}】", subscribedChannels);
            }
        } catch (Exception e) {
            throw Exceptions.error(e, "Redis 订阅失败，频道：【{}】", channels);
        }
    }

    /**
     * 取消订阅
     *
     * @param channels 频道
     */
    public void unsubscribe(Collection<String> channels) {
        if (log.isDebugEnabled()) {
            log.debug("订阅，频道：【{}】", channels);
        }
        if (channels == null || channels.isEmpty()) return;
        try (StatefulRedisPubSubConnection<String, Object> connection = pubSubPool.borrowObject()) {
            connection.sync().unsubscribe(ArrayUtil.toArray(channels, String.class));
        } catch (Exception e) {
            throw Exceptions.error(e, "Redis 取消订阅失败，频道：【{}】", channels);
        }
    }

    /**
     * 订阅（使用匹配模式）
     *
     * @param patterns 频道匹配模式（支持 glob 匹配规则）
     */
    public void psubscribe(Collection<String> patterns) {
        if (log.isDebugEnabled()) {
            log.debug("订阅（使用匹配模式），频道：【{}】", patterns);
        }
        Collection<String> newChannels = CollUtil.subtract(CollUtil.distinct(patterns), subscribedPatterns);
        try (StatefulRedisPubSubConnection<String, Object> connection = pubSubPool.borrowObject()) {
            if (newChannels.isEmpty()) return;
            connection.sync().psubscribe(ArrayUtil.toArray(newChannels, String.class));
            subscribedPatterns.addAll(newChannels);
            if (log.isDebugEnabled()) {
                log.debug("当前已订阅的频道（使用匹配模式）：【{}】", subscribedPatterns);
            }
        } catch (Exception e) {
            throw Exceptions.error(e, "Redis 订阅失败（使用匹配模式），频道：【{}】", patterns);
        }
    }

    /**
     * 取消订阅（使用匹配模式）
     *
     * @param patterns 频道匹配模式（支持 glob 匹配规则）
     */
    public void punsubscribe(Collection<String> patterns) {
        if (log.isDebugEnabled()) {
            log.debug("取消订阅（使用匹配模式），频道：【{}】", patterns);
        }
        if (patterns == null || patterns.isEmpty()) return;
        try (StatefulRedisPubSubConnection<String, Object> connection = pubSubPool.borrowObject()) {
            connection.sync().punsubscribe(ArrayUtil.toArray(patterns, String.class));
        } catch (Exception e) {
            throw Exceptions.error(e, "Redis 取消订阅失败，（使用匹配模式），频道：【{}】", patterns);
        }
    }
}
