package com.seer.teach.teacher.processor.chat;

import cn.hutool.json.JSONUtil;
import com.seer.teach.common.cache.LlmAudioPublisherService;
import com.seer.teach.common.dto.mq.LlmChatDTO;
import com.seer.teach.common.dto.mq.StringDTO;
import com.seer.teach.common.enums.im.ImMqTopicEnum;
import com.seer.teach.common.enums.im.RedisChannelPushTopTagEnum;
import com.seer.teach.teacher.common.llm.sse.req.LlmSseChatReq;
import com.seer.teach.teacher.common.utils.TeacherUtils;
import com.seer.teach.teacher.mq.ImMqProducer;
import com.seer.teach.teacher.processor.base.AbstractStreamProcessor;
import com.seer.teach.teacher.service.LlmSseService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import reactor.core.Disposable;
import reactor.core.publisher.Flux;

/**
 * 聊天流处理器，用于处理与大语言模型（LLM）交互的 SSE 流式数据。
 * 继承自 {@link AbstractStreamProcessor}，实现了聊天请求、响应及错误处理等生命周期方法。
 *
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class ChatStreamProcessor extends AbstractStreamProcessor<LlmSseChatReq, LlmChatDTO, String> {

    private final LlmAudioPublisherService llmAudioPublisherService;

    private final LlmSseService llmSseService;

    private final ImMqProducer imMqProducer;

    /**
     * 在流开始前执行的操作。
     * 发送一个初始化消息到 Redis 消息队列，通知客户端准备接收 LLM 音频数据。
     *
     * @param userId        用户ID
     * @param llmSseChatReq 聊天请求对象
     * @param device        设备标识
     * @param flux          数据流对象
     * @param channelName   通道名称
     */
    @Override
    protected void onBeforeStart(Integer userId, LlmSseChatReq llmSseChatReq, String device, Flux<LlmChatDTO> flux, String channelName) {
        imMqProducer.sendAudioChannelName(device, channelName);
    }

    /**
     * 当流启动时调用的方法。
     * 当前实现为空，可根据需要扩展逻辑。
     *
     * @param userId        用户ID
     * @param llmSseChatReq 聊天请求对象
     * @param s             其他字符串参数（如设备信息）
     */
    @Override
    protected void onStart(Integer userId, LlmSseChatReq llmSseChatReq, String s) {

    }

    /**
     * 处理从 LLM 返回的数据片段。
     * 将数据推送给指定设备，并在最终结果到达时保存聊天记录。
     *
     * @param userId      用户ID
     * @param device      设备标识
     * @param data        来自 LLM 的单个响应数据包
     * @param channelName 通道名称
     */
    @Override
    protected void onData(Integer userId, String device, LlmChatDTO data, String channelName) {
        data.setMqTime(System.currentTimeMillis());
        llmAudioPublisherService.publishToDevice(channelName, data);
    }

    /**
     * 处理流中的异常情况。
     * 记录错误日志并向客户端发送错误提示消息。
     *
     * @param userId      用户ID
     * @param device      设备标识
     * @param channelName 通道名称
     * @param error       异常对象
     */
    @Override
    protected void onError(Integer userId, String device, String channelName, Throwable error) {
        log.error("Llm 流式返回异常: userId={}, device={}, error={}", userId, device, error.getMessage(), error);
        // 向客户端推送LLM音频错误消息
        imMqProducer.sendMsg(
                ImMqTopicEnum.REDIS_CHANNEL_PUSH,
                RedisChannelPushTopTagEnum.PUSH_LLM_AUDIO_ERROR.getTag(),
                device,
                JSONUtil.toJsonStr(new StringDTO(channelName))
        );
    }

    /**
     * 注册当前用户的流订阅关系。
     * 使用服务将用户与对应的 Disposable 对象进行绑定管理。
     *
     * @param userId     用户ID
     * @param disposable 可取消的流资源对象
     */
    @Override
    protected void onRegister(Integer userId, Disposable disposable) {
        llmSseService.registerStream(userId, TeacherUtils.SSE_TYPE_CHAT, disposable);
    }
}