package com.seer.teach.netty.im.utils;

import com.seer.teach.common.dto.mq.LlmChatDTO;
import com.seer.teach.common.enums.MessagePushStatusEnums;
import com.seer.teach.netty.common.proto.MessageType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * RedisAudioChannelSubscriber 是一个用于订阅 Redis 音频频道并管理设备会话的组件。
 * 它负责监听指定频道的消息，将消息缓存到设备会话中，并定期批量推送至设备。
 * 同时支持动态添加、移除频道和设备，以及根据 ACK 延迟动态调整推送窗口大小。
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class RedisAudioChannelSubscriber {

    private final RedisMessageListenerContainer redisMessageListenerContainer;

    private final RedisTemplate<String, Object> redisTemplate;

    /**
     * 已订阅的频道集合，用于避免重复订阅。
     */
    private final Set<String> subscribedChannels = ConcurrentHashMap.newKeySet();

    /**
     * 频道与对应监听器的映射，用于后续移除监听器。
     */
    private final Map<String, MessageListener> listenerMap = new ConcurrentHashMap<>();

    /**
     * 设备与会话的映射，用于管理每个设备的音频数据流。
     */
    private final Map<String, DeviceSession> deviceSessions = new ConcurrentHashMap<>();

    /**
     * 推送窗口的最小批次大小。
     */
    public static final int MIN_BATCH = 45;

    /**
     * 推送窗口的最大批次大小。
     */
    public static final int MAX_BATCH = 90;


    /**
     * 异步订阅指定的音频频道，并为指定设备创建会话。
     * 如果频道已订阅，则直接返回。
     *
     * @param channelName 频道名称（不包含前缀）
     * @param device      设备标识
     */
    public void subscribeChannel(String channelName, String device) {

        if (!subscribedChannels.add(channelName)) {
            log.info("频道已订阅: {}", channelName);
            return;
        }

        DeviceSession session = new DeviceSession(device, channelName);
        deviceSessions.put(device, session);

        MessageListener listener = (message, pattern) -> {
            try {
                LlmChatDTO dto = (LlmChatDTO) redisTemplate.getValueSerializer().deserialize(message.getBody());

                // 过期判断
                if (!PushTimeStampUtils.isAfterLastTime(channelName, dto.getMqTime())) {
                    removeChannel(channelName);
                    session.cancel();
                    log.info("音频频道 [{}] 已过期，停止设备 [{}] 推送", channelName, device);
                    return;
                }

                session.addChunk(dto);

            } catch (Exception e) {
                log.error("处理音频消息异常 [{}]: {}", channelName, e.getMessage(), e);
            }
        };

        redisMessageListenerContainer.addMessageListener(listener, new ChannelTopic(channelName));
        listenerMap.put(channelName, listener);
        log.info("订阅 Redis 音频频道成功: {} -> 设备 {}", channelName, device);
    }

    /**
     * 移除指定设备的会话，停止其音频推送。
     *
     * @param device 设备标识
     */
    public void removeDevice(String device) {
        DeviceSession session = deviceSessions.remove(device);
        if (session != null) {
            session.cancel();
            log.info("设备 [{}] 已取消，停止推送", device);
        }
    }

    /**
     * 移除指定的频道订阅，并取消所有关联设备的推送。
     *
     * @param channelName 频道名称（包含前缀）
     * @return 是否成功移除
     */
    public boolean removeChannel(String channelName) {
        MessageListener listener = listenerMap.remove(channelName);
        if (listener != null) {
            try {
                redisMessageListenerContainer.removeMessageListener(listener, new ChannelTopic(channelName));
                subscribedChannels.remove(channelName);
                deviceSessions.values().stream().filter(s -> s.redisChannelName.equals(channelName)).forEach(DeviceSession::cancel);
                log.info("Redis 音频频道 [{}] 已移除，相关设备停止推送", channelName);
                return true;
            } catch (Exception e) {
                log.error("移除 Redis 音频频道 [{}] 异常: {}", channelName, e.getMessage(), e);
            }
        }
        return false;
    }

    /**
     * 定时任务：每 100 毫秒执行一次，将所有设备会话中的音频数据分批推送给设备。
     * 根据 ACK 延迟动态调整推送窗口大小。
     */
    @Scheduled(fixedRate = 100)
    private void dispatchAllDevices() {
        for (DeviceSession session : deviceSessions.values()) {
            if (session.cancelled.get() || session.queue.isEmpty()) {
                continue;
            }

            List<LlmChatDTO> batch = new ArrayList<>();
            for (int i = 0; i < session.window.get() && !session.queue.isEmpty(); i++) {
                LlmChatDTO data = session.queue.poll();
                // 判断data是否过期
                if (!PushTimeStampUtils.isAfterLastTime(session.redisChannelName, data.getMqTime())) {
                    removeChannel(session.redisChannelName);
                    session.cancel();
                    continue;
                }
                batch.add(data);
            }

            if (!batch.isEmpty()) {
                // 带ACK 确认机制 推送 delay 耗时
                long delay = ImUtils.pushSingleChatToDeviceWithAckTime(
                        session.device,
                        MessagePushStatusEnums.SUCCESS.getCode(),
                        MessageType.MessageContentType.RECEIVE_VOICE_STREAM.getMsgContentType(),
                        batch);
                updateAckStats(session, delay);
                log.info("Device:{} 推送区块大小: {} sessionId: {}  延时:{},SortId {}", session.getDevice(), batch.size(), batch.get(0).getId(), delay, batch.stream().map(LlmChatDTO::getSort).collect(Collectors.toList()));
            }
        }
    }

    /**
     * 根据 ACK 延迟动态调整推送窗口。
     * <p>
     * - 平滑处理延迟波动；
     * - 连续多次偏离阈值后才调整（防抖）；
     * - 自适应步长，偏差大调整幅度大。
     */
    private void updateAckStats(DeviceSession session, double delay) {
        final double alpha = 0.2;
        final double target = 400;
        final double hysteresis = 50;
        final int requiredConsecutive = 3;
        final int maxStep = 3;
        // 平滑平均延迟
        session.ackAvg = session.ackAvg * (1 - alpha) + delay * alpha;
        double deviation = session.ackAvg - target;
        // 在死区范围内，不调整
        if (Math.abs(deviation) < hysteresis) {
            session.stableCount.set(0);
            return;
        }
        // 增加连续偏差计数
        int count = session.stableCount.incrementAndGet();
        if (count < requiredConsecutive) {
            return;
        }
        session.stableCount.set(0);

        int window = session.window.get();
        int step = (int) Math.min(maxStep, Math.abs(deviation) / 100.0);
        if (deviation < -hysteresis && window < MAX_BATCH) {
            session.window.addAndGet(Math.min(step, MAX_BATCH - window));
            log.debug("窗口增大 [{}]: {} -> {}", session.device, window, session.window.get());
        } else if (deviation > hysteresis && window > MIN_BATCH) {
            session.window.addAndGet(-Math.min(step, window - MIN_BATCH));
            log.debug("窗口减小 [{}]: {} -> {}", session.device, window, session.window.get());
        }
    }

    /**
     * 移除所有频道订阅，清空所有设备会话。
     */
    public void removeAllChannels() {
        listenerMap.forEach((channel, listener) -> {
            try {
                redisMessageListenerContainer.removeMessageListener(listener, new ChannelTopic(channel));
            } catch (Exception e) {
                log.error("移除频道 [{}] 异常: {}", channel, e.getMessage());
            }
        });
        listenerMap.clear();
        subscribedChannels.clear();
        deviceSessions.values().forEach(DeviceSession::cancel);
        deviceSessions.clear();
        log.info("所有频道和设备已清理完毕");
    }
}
