package cn.novalon.gym.manage.checkIn.websocket;

import cn.hutool.json.JSONUtil;
import cn.novalon.gym.manage.checkIn.dto.QRCodeDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketSession;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks;

import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * WebSocket 处理类，用于实时签到反馈
 * 
 * 技术要点：
 * - 使用 Sinks 实现响应式消息推送
 * - 使用 ConcurrentHashMap 管理 qrContent 与 sink 的映射
 * - 支持实时推送签到进度和结果
 */
@Slf4j
@Component
public class MyWebSocketHandler implements WebSocketHandler {

    /**
     * qrContent -> Sink 映射，用于根据二维码内容找到对应的客户端连接
     */
    private static final Map<String, Sinks.Many<String>> qrContentToSink = new ConcurrentHashMap<>();

    /**
     * 连接创建时间映射，用于超时清理
     */
    private static final Map<String, LocalDateTime> qrContentToCreateTime = new ConcurrentHashMap<>();

    /**
     * 超时时间（秒），超过此时间未使用的连接将被清理
     */
    private static final long TIMEOUT_SECONDS = 300;

    @Override
    public Mono<Void> handle(WebSocketSession session) {
        String sessionId = session.getId();
        log.info("WebSocket 连接建立，sessionId: {}", sessionId);

        // 创建 sink，用于向客户端发送消息
        Sinks.Many<String> sink = Sinks.many().unicast().onBackpressureBuffer();

        // 订阅接收客户端消息（异步处理）
        session.receive()
                .doOnNext(message -> {
                    String payload = message.getPayloadAsText();
                    log.debug("收到消息：sessionId={}, payload={}", sessionId, payload);

                    try {
                        QRCodeDto qrCodeDto = JSONUtil.toBean(payload, QRCodeDto.class);
                        String qrContent = qrCodeDto.getQrContent();

                        if (qrContent != null && !qrContent.isEmpty()) {
                            // 绑定 qrContent 和 sink
                            qrContentToSink.put(qrContent, sink);
                            qrContentToCreateTime.put(qrContent, LocalDateTime.now());
                            log.info("绑定成功: qrContent={}, sessionId={}", qrContent, sessionId);
                            
                            // 发送连接成功消息
                            sink.tryEmitNext(buildMessage("CONNECTED", "签到监听已建立，请扫描二维码"));
                        } else {
                            sink.tryEmitNext(buildMessage("ERROR", "二维码内容为空"));
                        }
                    } catch (Exception e) {
                        log.error("解析消息失败，sessionId={}", sessionId, e);
                        sink.tryEmitNext(buildMessage("ERROR", "消息格式错误: " + e.getMessage()));
                    }
                })
                .doOnError(e -> {
                    log.error("接收消息出错，sessionId={}", sessionId, e);
                })
                .subscribe();  // 必须订阅，否则不会执行

        // 发送流给客户端
        return session.send(sink.asFlux().map(session::textMessage))
                .doFinally(signal -> {
                    // 连接关闭时清理映射
                    qrContentToSink.entrySet().removeIf(entry -> entry.getValue() == sink);
                    qrContentToCreateTime.entrySet().removeIf(entry -> {
                        Sinks.Many<String> s = qrContentToSink.get(entry.getKey());
                        return s == null || s == sink;
                    });
                    log.info("WebSocket 连接关闭，sessionId={}", sessionId);
                });
    }

    /**
     * 向客户端发送消息
     *
     * @param qrContent 二维码内容
     * @param message 消息内容
     * @return 是否发送成功
     */
    public static boolean sendMessageToClient(String qrContent, String message) {
        // 先清理超时连接
        cleanupTimeoutConnections();

        Sinks.Many<String> sink = qrContentToSink.get(qrContent);
        if (sink == null) {
            log.warn("未找到绑定的连接，qrContent: {}", qrContent);
            return false;
        }

        Sinks.EmitResult result = sink.tryEmitNext(message);
        if (result.isSuccess()) {
            log.info("主动推送成功，qrContent: {}, message: {}", qrContent, message);
            return true;
        } else {
            log.warn("推送失败，qrContent: {}, result: {}", qrContent, result);
            return false;
        }
    }

    /**
     * 发送签到进度消息
     *
     * @param qrContent 二维码内容
     * @param step 进度步骤
     * @param message 进度消息
     */
    public static void sendProgress(String qrContent, String step, String message) {
        String progressMessage = buildMessage("PROGRESS", message);
        sendMessageToClient(qrContent, progressMessage);
        log.debug("发送进度消息: qrContent={}, step={}, message={}", qrContent, step, message);
    }

    /**
     * 发送签到成功消息
     *
     * @param qrContent 二维码内容
     * @param memberId 会员ID
     * @param signInTime 签到时间
     */
    public static void sendSuccess(String qrContent, Long memberId, String signInTime) {
        String successMessage = buildMessage("SUCCESS", "签到成功！欢迎光临\n会员ID: " + memberId + "\n签到时间: " + signInTime);
        sendMessageToClient(qrContent, successMessage);
        log.info("发送成功消息: qrContent={}, memberId={}", qrContent, memberId);
    }

    /**
     * 发送签到失败消息
     *
     * @param qrContent 二维码内容
     * @param reason 失败原因
     */
    public static void sendFailure(String qrContent, String reason) {
        String failureMessage = buildMessage("FAILURE", "签到失败：" + reason);
        sendMessageToClient(qrContent, failureMessage);
        log.warn("发送失败消息: qrContent={}, reason={}", qrContent, reason);
    }

    /**
     * 构建标准消息格式
     *
     * @param type 消息类型
     * @param content 消息内容
     * @return 格式化后的消息字符串
     */
    private static String buildMessage(String type, String content) {
        return JSONUtil.toJsonStr(Map.of(
                "type", type,
                "content", content,
                "timestamp", System.currentTimeMillis()
        ));
    }

    /**
     * 清理超时连接
     */
    private static void cleanupTimeoutConnections() {
        LocalDateTime now = LocalDateTime.now();
        qrContentToCreateTime.entrySet().removeIf(entry -> {
            LocalDateTime createTime = entry.getValue();
            long secondsDiff = java.time.Duration.between(createTime, now).getSeconds();
            if (secondsDiff > TIMEOUT_SECONDS) {
                String qrContent = entry.getKey();
                qrContentToSink.remove(qrContent);
                log.debug("清理超时连接: qrContent={}, 超时时间={}秒", qrContent, secondsDiff);
                return true;
            }
            return false;
        });
    }

    /**
     * 获取当前连接数
     *
     * @return 连接数
     */
    public static int getConnectionCount() {
        cleanupTimeoutConnections();
        return qrContentToSink.size();
    }
}
