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 com.seer.teach.teacher.service.impl.ReadCountdownManagerService;
import com.seer.teach.teacher.service.impl.ReadEndService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import reactor.core.Disposable;
import reactor.core.publisher.Flux;

/**
 * ReadChatStreamProcessor 是一个用于处理阅读时聊天流数据的处理器类。
 * 它继承自 AbstractStreamProcessor，专门用于处理与朗读相关的聊天流逻辑，
 * 包括数据传输、错误处理、完成回调等操作。
 *
 * <p>该类主要负责以下功能：</p>
 * <ul>
 *   <li>在流开始时执行初始化操作；</li>
 *   <li>接收并转发流中的数据到指定设备；</li>
 *   <li>处理流过程中的异常情况并向客户端推送错误信息；</li>
 *   <li>注册当前用户的流订阅关系以便管理；</li>
 *   <li>在流完成后触发倒计时结束通知。</li>
 * </ul>
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class ReadChatStreamProcessor extends AbstractStreamProcessor<LlmSseChatReq, LlmChatDTO, String> {

    private final LlmAudioPublisherService llmAudioPublisherService;

    private final ReadCountdownManagerService readCountdownManagerService;

    private final ReadEndService readEndService;

    private final ImMqProducer imMqProducer;

    private final LlmSseService llmSseService;

    /**
     * 在正式业务处理之前执行前置操作：异步更新推送时间和发送初始推送信号。
     *
     * @param userId    用户ID
     * @param llmReq    请求参数对象
     * @param device    设备标识
     * @param flux      数据流对象
     * @param channelName 频道名称
     */
    @Override
    protected void onBeforeStart(Integer userId, LlmSseChatReq llmReq, String device, Flux<LlmChatDTO> flux, String channelName) {
        imMqProducer.sendAudioChannelName(device,channelName);
    }

    /**
     * 当流处理完成时调用此方法。会启动一个倒计时任务，在倒计时结束后向用户推送“朗读结束”消息。
     *
     * @param userId         用户ID
     * @param llmSseChatReq  聊天请求对象
     * @param device         设备标识
     * @param s              最终结果字符串（未使用）
     */
    @Override
    protected void onComplete(Integer userId, LlmSseChatReq llmSseChatReq, String device, String s) {
        readCountdownManagerService.registerCountdown(userId, 5, () -> {
            readEndService.pushReadEnd(userId, device);
        });
    }

    /**
     * 在流开始前调用的方法，可用于初始化资源或记录日志。
     *
     * @param userId        用户ID
     * @param llmSseChatReq 请求参数对象
     * @param channelName   通道名称
     */
    @Override
    protected void onStart(Integer userId, LlmSseChatReq llmSseChatReq, String channelName) {
    }

    /**
     * 处理从流中接收到的数据，并将其发布至目标设备。
     *
     * @param userId      用户ID
     * @param device      目标设备标识
     * @param data        接收的数据对象
     * @param channelName 数据发布的频道名
     */
    @Override
    protected void onData(Integer userId, String device, LlmChatDTO data, String channelName) {
        data.setMqTime(System.currentTimeMillis());
        llmAudioPublisherService.publishToDevice(channelName, data);
    }

    /**
     * 当流发生异常时被调用，将错误信息通过MQ推送给前端。
     *
     * @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))
        );
    }

    /**
     * 注册当前用户的流订阅关系，便于后续管理和取消订阅。
     *
     * @param userId       用户ID
     * @param disposable   可取消的订阅对象
     */
    @Override
    protected void onRegister(Integer userId, Disposable disposable) {
        llmSseService.registerStream(userId, TeacherUtils.SSE_TYPE_CHAT, disposable);
    }

}
