package com.seer.teach.teacher.service.impl;

import com.seer.teach.common.cache.SseLifeCycleCacheService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.SignalType;

import java.time.Duration;

/**
 * 提供 Server-Sent Events (SSE) 流式通信功能的服务类。
 * 支持通过 WebClient 发起 SSE 请求，并结合 Redis 缓存管理用户会话状态。
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SseFactoryService {

    private final WebClient webClient;

    private final SseLifeCycleCacheService sseLifeCycleCacheService;


    /**
     * 发起 SSE（Server-Sent Events）流式请求，并返回响应的 Flux 流。
     * 该方法会根据传入的参数构建 WebClient 请求，设置超时时间、请求体及响应类型，
     * 并在建立连接、完成、出错或被取消时维护 Redis 中的用户会话状态。
     *
     * @param uri          请求的目标 URI
     * @param request      请求体对象，将被序列化为 JSON 格式发送
     * @param method       HTTP 方法（如 GET、POST）
     * @param redisKey     用于标识用户的 Redis 键名，用于缓存会话信息
     * @param userId       用户 ID，用于记录当前 SSE 连接所属用户
     * @param sessionId    当前 SSE 连接的唯一标识符
     * @param timeOut      超时时间（单位：秒），如果为 null 则不设置超时
     * @param responseType 响应数据的类型，用于反序列化服务器返回的数据
     * @param <T>          请求体泛型类型
     * @param <R>          响应体泛型类型
     * @return 返回一个 Flux<R> 流，表示持续接收的服务端事件流
     */
    public  <T, R> Flux<R> streamSse(String uri,
                                           T request,
                                           HttpMethod method,
                                           String redisKey,
                                           Integer userId,
                                           Integer sessionId,
                                           Integer timeOut,
                                           Class<R> responseType) {
        String bizName = redisKey.replace(":", "");
        return webClient
                .method(method)
                .uri(uri)
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.TEXT_EVENT_STREAM)
                .bodyValue(request)
                .retrieve()
                .bodyToFlux(responseType)
                // 设置超时时间
                .timeout(Duration.ofSeconds(timeOut))
                // 在订阅开始时保存会话到 Redis
                .doOnSubscribe(sub -> {
                    sseLifeCycleCacheService.saveSession(redisKey, userId, sessionId);
                    log.info("{} 连接成功, redisKey={}, sseId={}, userId={},sessionId:{}", bizName, redisKey, sessionId, userId,sessionId);
                })
                // 在流正常结束时移除 Redis 中的会话
                .doOnComplete(() -> {
                    sseLifeCycleCacheService.removeSession(redisKey, userId);
                    log.info("{} 连接结束, redisKey={}, sseId={}, userId={},sessionId:{}", bizName, redisKey, sessionId, userId,sessionId);
                })
                // 在发生错误时清理 Redis 会话并记录日志
                .doOnError(error -> {
                    sseLifeCycleCacheService.removeSession(redisKey, userId);
                    log.error("{}连接出错, redisKey={}, sseId={}, userId={}, error={},sessionId:{}", bizName, redisKey, sessionId, userId, error,sessionId);
                })
                // 在流被取消时进行资源清理
                .doFinally(signal -> {
                    if (signal == SignalType.CANCEL) {
                        sseLifeCycleCacheService.removeSession(redisKey, userId);
                        log.warn("{} 连接取消, redisKey={}, sseId={}, userId={}, signal={},sessionId:{}", bizName, redisKey, sessionId, userId, signal,sessionId);
                    }
                });
    }

    /**
     * 流式处理SSE(Server-Sent Events)请求，默认使用 POST 方法发起请求。
     *
     * @param <T>          请求体类型
     * @param <R>          响应体类型
     * @param uri          请求的URI地址
     * @param request      请求体对象
     * @param redisKey     Redis缓存键值
     * @param userId       用户ID
     * @param sessionId    会话ID
     * @param timeOut      超时时间
     * @param responseType 响应体类型Class对象
     * @return Flux<R> 响应式流对象
     */
    public <T, R> Flux<R> streamSse(
            String uri,
            T request,
            String redisKey,
            Integer userId,
            Integer sessionId,
            Integer timeOut,
            Class<R> responseType) {
        return streamSse(uri, request, HttpMethod.POST, redisKey, userId, sessionId, timeOut, responseType);
    }


}
