package cn.reghao.tnb.message.app.ws.media;

import cn.reghao.jutil.jdk.serializer.JdkSerializer;
import cn.reghao.jutil.jdk.serializer.JsonConverter;
import cn.reghao.tnb.auth.api.constant.TokenType;
import cn.reghao.tnb.auth.api.dto.AuthedAccount;
import cn.reghao.tnb.auth.api.iface.AccountQuery;
import cn.reghao.tnb.message.api.dto.MediaProgress;
import cn.reghao.tnb.message.api.model.EventType;
import cn.reghao.tnb.message.api.model.resp.EventMessageResp;
import cn.reghao.tnb.message.app.config.SpringLifecycle;
import cn.reghao.tnb.message.app.rabbit.RabbitProducer;
import cn.reghao.tnb.message.app.redis.RedisKeys;
import cn.reghao.tnb.message.app.redis.ds.RedisHash;
import cn.reghao.tnb.message.app.redis.ds.RedisOps;
import cn.reghao.tnb.message.app.ws.chat.msg.EventResp;
import cn.reghao.tnb.message.app.ws.config.PingPayload;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.BinaryMessage;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @author reghao
 * @date 2024-12-02 11:36:31
 */
@Slf4j
@Component
public class MediaService {
    @DubboReference(check = false, retries = 0, timeout = 60_000)
    private AccountQuery accountQuery;

    private final RabbitProducer rabbitProducer;
    private final RedisHash<Long> redisHash;
    private final RedisOps redisOps;
    // userId -> session
    private final Map<Long, WebSocketSession> userSessions = new HashMap<>();
    // sessionId -> userId
    private final Map<String, Long> sessionUsers = new HashMap<>();
    // sessionId -> mediaProgress
    private final Map<String, MediaProgress> map = new HashMap<>();
    private final SpringLifecycle springLifecycle;

    public MediaService(RabbitProducer rabbitProducer, RedisHash<Long> redisHash,
                        RedisOps redisOps, SpringLifecycle springLifecycle) {
        this.rabbitProducer = rabbitProducer;
        this.redisHash = redisHash;
        this.redisOps = redisOps;
        this.springLifecycle = springLifecycle;
    }

    public boolean addSession(WebSocketSession webSocketSession) {
        String sessionId = webSocketSession.getId();
        String userToken = (String) webSocketSession.getAttributes().get("userToken");
        String videoId = (String) webSocketSession.getAttributes().get("videoId");
        AuthedAccount authedAccount = accountQuery.getAuthedAccount(TokenType.token.getValue(), userToken);
        if (authedAccount != null) {
            String loginId = authedAccount.getLoginId();
            long userId = authedAccount.getUserId();

            String nodeId = springLifecycle.getNodeId();
            String redisKey = RedisKeys.getVideoUsersKey(videoId, nodeId);
            redisHash.hset(redisKey, sessionId, userId);

            addUserSession(userId, webSocketSession);
            check(videoId, nodeId);
            sendViewEvent(videoId);
            return true;
        }
        return false;
    }

    private void addUserSession(long userId, WebSocketSession webSocketSession) {
        String sessionId = webSocketSession.getId();
        userSessions.put(userId, webSocketSession);
        sessionUsers.put(sessionId, userId);
    }

    private void check(String videoId, String nodeId) {
        String keyPattern = RedisKeys.getVideoUsersKey(videoId) + ":*";
        Set<String> redisKeys = redisOps.keys(keyPattern);
        for (String redisKey : redisKeys) {
            if (redisKey.endsWith(nodeId)) {
                Map<String, Long> redisMap = redisHash.hgetall(redisKey);
                Set<String> redisSessionSet = redisMap.keySet();
                Set<String> localSessionSet = sessionUsers.keySet();
                boolean ret = redisSessionSet.removeAll(localSessionSet);
                if (ret) {
                    for (String hashKey : redisSessionSet) {
                        redisHash.delete(redisKey, hashKey);
                    }
                }
            }
        }
    }

    public void removeSession(WebSocketSession webSocketSession) {
        String sessionId = webSocketSession.getId();
        MediaProgress mediaProgress = map.remove(sessionId);
        if (mediaProgress != null) {
            persistProgress(sessionId, mediaProgress);
        }

        Long userId = sessionUsers.get(sessionId);
        userSessions.remove(userId);
        sessionUsers.remove(sessionId);

        String videoId = (String) webSocketSession.getAttributes().get("videoId");
        String nodeId = springLifecycle.getNodeId();
        String redisKey = RedisKeys.getVideoUsersKey(videoId, nodeId);
        redisHash.delete(redisKey, sessionId);
        sendViewEvent(videoId);
    }

    public Long getUserId(String sessionId) {
        return sessionUsers.get(sessionId);
    }

    public void sendPingPayload(WebSocketSession session) throws IOException {
        PingPayload pingPayload = new PingPayload(60, 120);
        EventResp eventResp = new EventResp();
        eventResp.setEvent("connect");
        eventResp.setPayload(pingPayload);

        TextMessage textMessage = new TextMessage(JsonConverter.objectToJson(eventResp));
        session.sendMessage(textMessage);
    }

    public void sendHeartbeatPong(WebSocketSession session) throws IOException {
        EventMessageResp<String> resp = new EventMessageResp<>(EventType.heartbeat, "pong");
        TextMessage textMessage = new TextMessage(JsonConverter.objectToJson(resp));
        session.sendMessage(textMessage);
    }

    public void sendViewEvent(String videoId) {
        sendViewCount(videoId);

        Map<String, Object> wsMessage = new HashMap<>();
        wsMessage.put("broadcastBy", springLifecycle.getNodeId());
        wsMessage.put("type", "sendViewCount");
        wsMessage.put("videoId", videoId);
        broadcastCluster(JsonConverter.objectToJson(wsMessage));
    }

    public void send(String receiverId, Object message) throws IOException {
        WebSocketSession session = userSessions.get(receiverId);
        if (session != null) {
            byte[] bytes = JdkSerializer.serialize(message);
            BinaryMessage binaryMessage = new BinaryMessage(bytes);
            session.sendMessage(binaryMessage);
        }
    }

    public void sendViewCount(String videoId) {
        String keyPattern = RedisKeys.getVideoUsersKey(videoId) + ":*";
        Set<String> redisKeys = redisOps.keys(keyPattern);
        Set<Long> userSet = new HashSet<>();
        for (String redisKey : redisKeys) {
            Map<String, Long> redisMap = redisHash.hgetall(redisKey);
            userSet.addAll(new HashSet<>(redisMap.values()));
        }

        int viewCount = userSet.size();
        Map<String, Object> message = new HashMap<>();
        message.put("viewCount", viewCount);
        userSet.forEach(userId -> {
            WebSocketSession session = userSessions.get(userId);
            if (session != null) {
                String json = JsonConverter.objectToJson(message);
                TextMessage textMessage = new TextMessage(json);
                try {
                    session.sendMessage(textMessage);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * 集群中广播
     *
     * @param
     * @return
     * @date 2025-10-11 20:10:127
     */
    public void broadcastCluster(String payload) {
        rabbitProducer.broadcastWsMessage(payload);
    }

    public void putMediaProgress(String sessionId, MediaProgress mediaProgress) {
        map.put(sessionId, mediaProgress);
        if (mediaProgress.isEnded()) {
            persistProgress(sessionId, mediaProgress);
        }
    }

    public void persistProgress(String sessionId, MediaProgress mediaProgress) {
        Long userId = getUserId(sessionId);
        if (mediaProgress != null) {
            mediaProgress.setUserId(userId);
            rabbitProducer.sendMediaProgress(mediaProgress);
        }
    }
}
