package com.ssyf.task.websocket;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.ssyf.common.core.func.Callback;
import com.ssyf.common.core.func.CallbackP;
import com.ssyf.task.websocket.msg.WsMessageDispatcher;
import com.ssyf.third.bmiss.core.EncryptionManager;
import com.ssyf.third.bmiss.model.LiveRoom;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PreDestroy;
import java.net.URI;
import java.util.*;
import java.util.concurrent.*;

@Slf4j
@Service
public class LiveRoomWsManager {

    @Autowired private GlobalWsCoordinator coordinator;
    @Autowired private WsStatisticsService statsService;
    @Autowired private WsMessageDispatcher dispatcher;
    @Autowired private RobotWsProperties properties;
    // 注入加密管理器，用于获取密钥生成签名
    @Autowired private EncryptionManager encryptionManager;
    @Autowired
    private WsStatisticsService wsStatisticsService;

    // 连接池
    private final Map<Integer, LiveRoomWsClient> connectionPool = new ConcurrentHashMap<>();

    // 重试次数记录表: RoomID -> RetryCount
    private final Map<Integer, Integer> retryCounter = new ConcurrentHashMap<>();

    // 重连调度器 (单线程即可，任务量不大)
    private final ScheduledExecutorService reconnectScheduler = Executors.newSingleThreadScheduledExecutor();

    /**
     * 外部调用的入口：确保连接
     */
    public void ensureConnection(LiveRoom liveRoom, String token , CallbackP<String> callback) {
        Integer roomId = liveRoom.getUser_id();

        // 重置重试计数，因为这是一次新的主动请求
        retryCounter.remove(roomId);

        //判断是否已经开启了ws连接
        if (connectionPool.containsKey(roomId) && connectionPool.get(roomId).isOpen()) {
            notConnectCallback(liveRoom , token , callback);
            return;
        }

        if (coordinator.shouldConnect(roomId)) {
            //都添加到队列中等待回调。
            wsStatisticsService.registerCallback(liveRoom.getUser_id(), callback);
            connectAsync(liveRoom, token);
        }else{
            notConnectCallback(liveRoom , token , callback);
        }
    }

    private void notConnectCallback(LiveRoom liveRoom, String token , CallbackP<String> callback) {
        Map<Object, Object> map = wsStatisticsService.getByRoomId(liveRoom.getUser_id());
        if (CollUtil.isNotEmpty(map)) {
            if(map.containsKey("clientId")){
                callback.call(map.get("clientId").toString());
                return;
            }
        }

        //都添加到队列中等待回调。
        wsStatisticsService.registerCallback(liveRoom.getUser_id(), callback);
    }

    private void connectAsync(LiveRoom liveRoom, String token) {
        //不需要异步处理，必须先建立ws连接才能调用
//        ThreadUtil.execAsync(() -> {
            Integer roomId = liveRoom.getUser_id();
            try {
                URI uri = buildWsUri(properties.getServerUrl(), token);

                //打印url
                log.info("[Room {}] Connecting to {}", roomId, uri);
                LiveRoomWsClient client = new LiveRoomWsClient(
                        uri, roomId, token,
                        (msg) -> dispatcher.dispatch(liveRoom ,token, msg),
                        (id) -> handleClose(liveRoom, token),
                        () -> coordinator.refreshState(roomId, liveRoom.getNick_name())
                );

                // 使用配置的超时时间
                client.connectBlocking(properties.getConnectTimeoutSeconds(), TimeUnit.SECONDS);

                if (client.isOpen()) {
                    connectionPool.put(roomId, client);
                    coordinator.refreshState(roomId, liveRoom.getNick_name());
                    statsService.recordConnect(roomId, liveRoom.getTitle(), liveRoom.getNick_name());
                    log.info("WS Connected: Room {}", roomId);
                } else {
                    log.warn("WS Connect Failed: Room {}", roomId);
                    // 触发重连
                    handleClose(liveRoom, token);
                }

            } catch (Exception e) {
                log.error("WS Create Error: Room {}", roomId, e);
                handleClose(liveRoom, token);
            }
//        });
    }

    /**
     * 逻辑对齐 RequestManager.generateCommonParamsForHeaders
     */
    private URI buildWsUri(String baseUrl, String token) throws Exception {
        // 1. 获取基础参数
        Map<String, String> params = new HashMap<>();
        params.put("device", "wap");
        params.put("source", "com.sanya.xinxiulive.lite");
        params.put("request_number", generateRequestNumber());
        params.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
        params.put("appversion", "1.0.0");

        if (StrUtil.isNotBlank(token)) {
            params.put("token", token);
        }

        // 2. 获取签名密钥并添加 sign_name
        // 假设 EncryptionManager 有类似 selectRandomSecretKey 的方法返回 KeyInfo
        EncryptionManager.SecretKeyInfo secretKeyInfo = encryptionManager.selectRandomSecretKey();
        String signName = secretKeyInfo.getKey(); // 根据实际 EncryptionManager 实现调整，可能是 getKey() 或 getName()
        if (StrUtil.isNotBlank(signName)) {
            params.put("sign_name", signName);
        }

        // 3. 生成签名 (双重MD5)
        String signature = generateSignature(params);
        params.put("signature", signature);

        // 4. 拼接到 URL 后面
        StringBuilder queryString = new StringBuilder();
        for (Map.Entry<String, String> entry : params.entrySet()) {
            if (queryString.length() > 0) {
                queryString.append("&");
            }
            queryString.append(entry.getKey()).append("=").append(entry.getValue());
        }

        // 处理 baseUrl 可能已经带参数的情况
        String finalUrl = baseUrl;
        if (baseUrl.contains("?")) {
            finalUrl += "&" + queryString.toString();
        } else {
            finalUrl += "?" + queryString.toString();
        }

        return new URI(finalUrl);
    }

    /**
     * 【新增】生成请求编号 (逻辑复刻 RequestManager)
     */
    private String generateRequestNumber() {
        return "java" + ThreadLocalRandom.current().nextInt(10000, 99999);
    }

    /**
     * 【新增】生成双重MD5签名 (逻辑复刻 RequestManager)
     */
    private String generateSignature(Map<String, String> params) {
        try {
            // 按参数名排序
            List<String> sortedKeys = new ArrayList<>(params.keySet());
            Collections.sort(sortedKeys);

            // 拼接字符串 key=value&key=value
            StringBuilder sb = new StringBuilder();
            for (String key : sortedKeys) {
                if (!"signature".equals(key)) { // 排除签名本身
                    sb.append(key).append("=").append(params.get(key)).append("&");
                }
            }

            // 移除最后一个&符号
            if (sb.length() > 0) {
                sb.setLength(sb.length() - 1);
            }

            String signString = sb.toString();

            // 双重MD5加密
            String firstMd5 = DigestUtil.md5Hex(signString);
            String secondMd5 = DigestUtil.md5Hex(firstMd5);

            return secondMd5;
        } catch (Exception e) {
            log.error("WS生成签名失败", e);
            return "";
        }
    }


    /**
     * 处理关闭/异常，并决策是否重连
     */
    private void handleClose(LiveRoom liveRoom, String token) {
        Integer roomId = liveRoom.getUser_id();

        // 1. 清理资源
        connectionPool.remove(roomId);
        coordinator.releaseState(roomId);
        statsService.recordDisconnect(roomId);

        // 2. 检查是否应该重连
        // 策略：如果分布式协调器认为该房间仍应由我持有（或者无人持有），且未达到最大重试次数，则重连
        // 这里简化逻辑：只要房间还在直播(外部判断)且异常断开，就尝试重连。
        // 但为了防止无限死循环，我们使用计数器。

        int attempts = retryCounter.getOrDefault(roomId, 0) + 1;

        if (properties.getMaxRetryAttempts() > 0 && attempts > properties.getMaxRetryAttempts()) {
            log.error("Room {} reached max retry attempts ({}), giving up.", roomId, attempts);
            retryCounter.remove(roomId);
            return;
        }

        // 3. 计算延迟 (指数退避 + 随机抖动)
        // delay = base * 2^(attempts-1)
        long delay = properties.getRetryBaseDelayMs() * (1L << (Math.min(attempts, 10) - 1));
        // 封顶
        delay = Math.min(delay, properties.getRetryMaxDelayMs());
        // 抖动 +/- 20%
        delay = delay + RandomUtil.randomLong(-delay / 5, delay / 5);
        if(delay < 0) {
            delay = 1000;
        }

        log.info("Scheduling reconnect for Room {} in {} ms (Attempt {})", roomId, delay, attempts);

        retryCounter.put(roomId, attempts);
        statsService.recordReconnectAttempt(roomId);

        reconnectScheduler.schedule(() -> {
            // 再次检查分布式锁，防止在等待期间其他节点已经接管了
            if (coordinator.shouldConnect(roomId)) {
                connectAsync(liveRoom, token);
            }
        }, delay, TimeUnit.MILLISECONDS);
    }

    /**
     * 关闭指定直播间的WebSocket连接
     * 
     * @param roomId 直播间ID
     */
    public void closeRoomConnection(Integer roomId) {
        if (roomId == null) {
            return;
        }

        LiveRoomWsClient client = connectionPool.get(roomId);
        if (client != null) {
            try {
                client.close();
                connectionPool.remove(roomId);
                coordinator.releaseState(roomId);
                statsService.recordDisconnect(roomId);
                log.info("WebSocket连接已强制关闭，roomId: {}", roomId);
            } catch (Exception e) {
                log.error("关闭WebSocket连接异常，roomId: {}", roomId, e);
            }
        } else {
            log.debug("WebSocket连接不存在，无需关闭，roomId: {}", roomId);
        }
    }

    @PreDestroy
    public void destroy() {
        connectionPool.values().forEach(LiveRoomWsClient::close);
        reconnectScheduler.shutdownNow();
    }
}