package com.njzscloud.common.redis.support;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ClassUtil;
import com.njzscloud.common.core.fastjson.Fastjson;
import com.njzscloud.common.core.tuple.Tuple2;
import com.njzscloud.common.redis.RedisCli;
import com.njzscloud.common.redis.annotation.RedisChannel;
import io.lettuce.core.pubsub.RedisPubSubListener;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * Redis 发布订阅模式，消息派发器
 */
@Slf4j
public class RedisMessageDispatch implements RedisPubSubListener<String, Object> {
    private final RedisCli redisCli;

    /**
     * 频道/频道模式 —— 监听器对象，消息处理方法
     */
    Map<String, List<Tuple2<Object, Method>>> dispatchList = new HashMap<>();

    /**
     * 构建消息派发器
     *
     * @param redisCli Redis 客户端 {@link RedisCli}
     */
    public RedisMessageDispatch(RedisCli redisCli) {
        this.redisCli = redisCli;
        this.redisCli.addListener(this);
    }

    @Override
    public void message(String channel, Object message) {
        message(null, channel, message);
    }

    @Override
    public void message(String pattern, String channel, Object message) {
        if (log.isDebugEnabled()) {
            log.debug("收到 Redis 订阅消息，订阅模式：【{}】、订阅频道：【{}】、消息内容：【{}】", pattern, channel, Fastjson.toJsonStr(message));
        }

        List<Tuple2<Object, Method>> methodList;

        if (pattern == null) methodList = dispatchList.get(channel);
        else methodList = dispatchList.get(pattern);

        if (methodList == null || methodList.isEmpty()) return;

        for (Tuple2<Object, Method> tuple2 : methodList) {
            Object target = tuple2.get_0();
            Method method = tuple2.get_1();

            int parameterCount = method.getParameterCount();
            Object[] parameters = new Object[parameterCount];

            if (parameterCount == 1) {
                parameters[0] = message;
            } else if (parameterCount == 2) {
                parameters[0] = channel;
                parameters[1] = message;
            } else if (parameterCount == 3) {
                parameters[0] = pattern;
                parameters[1] = channel;
                parameters[2] = message;
            }

            CompletableFuture.runAsync(() -> {
                try {
                    method.invoke(target, parameters);
                } catch (Exception e) {
                    log.error("Redis 订阅消息处理失败，订阅模式：【{}】、订阅频道：【{}】、消息内容：【{}】", pattern, channel, Fastjson.toJsonStr(message), e);
                }
            });
        }
    }

    @Override
    public void subscribed(String channel, long count) {
        log.info("订阅，频道：{}、订阅数：{}", channel, count);
    }

    @Override
    public void psubscribed(String pattern, long count) {
        log.info("订阅，模式：{}、订阅数：{}", pattern, count);
    }

    @Override
    public void unsubscribed(String channel, long count) {
        log.info("取消订阅，频道：{}、剩余订阅数：{}", channel, count);
    }

    @Override
    public void punsubscribed(String pattern, long count) {
        log.info("取消订阅，模式：{}、剩余订阅数：{}", pattern, count);
    }

    public void subscribe(Object listener) {
        Class<?> clazz = listener.getClass();
        HashSet<String> exclude = CollUtil.newHashSet("wait", "equals", "toString", "hashCode", "getClass", "notify", "notifyAll");
        Method[] methods = ClassUtil.getPublicMethods(clazz, it -> {
            int modifiers = it.getModifiers();
            int parameterCount = it.getParameterCount();
            String name = it.getName();
            return !exclude.contains(name) && !Modifier.isStatic(modifiers) && !Modifier.isAbstract(modifiers) && (parameterCount == 0 || parameterCount == 1 || parameterCount == 3);
        }).toArray(new Method[0]);
        if (methods.length == 0) return;

        Set<String> registrationPatterns = new HashSet<>();
        Set<String> registrationChannels = new HashSet<>();

        String clazzName = clazz.getName();
        for (Method method : methods) {
            RedisChannel redisChannel = method.getAnnotation(RedisChannel.class);
            if (redisChannel == null) continue;

            Tuple2<Object, Method> listenerTuple = Tuple2.create(listener, method);

            registrationPatterns.addAll(resolveListener(listenerTuple, redisChannel.patterns(), dispatchList));

            registrationChannels.addAll(resolveListener(listenerTuple, redisChannel.channels(), dispatchList));

            if (log.isDebugEnabled()) {
                log.debug("Redis 监听器，事件处理方法已注册：【{}#{}】", clazzName, method.getName());
            }
        }

        this.redisCli.psubscribe(registrationPatterns);
        this.redisCli.subscribe(registrationChannels);
    }

    /**
     * 计算要订阅的频道或频道模式
     */
    private Set<String> resolveListener(Tuple2<Object, Method> listener, String[] channels, Map<String, List<Tuple2<Object, Method>>> dispatchList) {
        if (channels == null || channels.length == 0) return Collections.emptySet();

        Set<String> channelSet = Arrays.stream(channels).collect(Collectors.toSet());

        Set<String> registrations = new HashSet<>(channelSet);

        for (String channel : channelSet) {
            List<Tuple2<Object, Method>> listeners = dispatchList.computeIfAbsent(channel, k -> new ArrayList<>());
            listeners.add(listener);
        }

        return registrations;
    }
}
