package com.sikey.wa04.business.infrastructure.adapter;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.sikey.wa04.business.domain.entity.UserId;
import com.sikey.wa04.business.domain.entity.websocket.Connection;
import com.sikey.wa04.business.infrastructure.configuration.WebSocketConfig;
import com.sikey.wa04.common.dto.message.HeartbeatMessage;
import com.sikey.wa04.common.enums.MessageType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.io.Serializable;
import java.time.Instant;
import java.util.List;
import java.util.Objects;

/**
 * 缓存连接状态管理
 *
 * @author luoyangwei
 */
@Slf4j
@Component
public class CacheConnectionStateManager {
    private static final String CONNECTION_KEY = "connection.";
    private final ObjectMapper objectMapper = initializeObjectMapper();

    private final RedisOperations<String, String> operations;
    private final WebSocketConfig webSocketConfig;

    @Autowired
    public CacheConnectionStateManager(RedisOperations<String, String> operations, WebSocketConfig webSocketConfig) {
        this.operations = operations;
        this.webSocketConfig = webSocketConfig;
    }

    private final Cache<UserId, Entry> cache = CacheBuilder
            .newBuilder()
            .maximumSize(10000L)
            .build();

    private record Entry(Connection connection, WebSocketSession session) {
    }

    /**
     * 初始化ObjectMapper
     *
     * @return ObjectMapper
     */
    private ObjectMapper initializeObjectMapper() {
        ObjectMapper objectMapper = new Jackson2ObjectMapperBuilder()
                .createXmlMapper(false)
                .failOnEmptyBeans(false)
                .failOnUnknownProperties(false)
                .build();
        objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
        return objectMapper;
    }

    /**
     * 下线
     *
     * @param connection 连接
     */
    public void offline(Connection connection) {
        try {
            Objects.requireNonNull(cache.getIfPresent(connection.getId())).session.close(CloseStatus.NORMAL);
            cache.invalidate(connection.getId());
            operations.delete(buildConnectionKey(connection.getId()));
        } catch (IOException e) {
            log.error("Close session error", e);
        }
    }

    /**
     * 上线
     *
     * @param connection 上线
     * @param session    WebSocket 会话
     * @throws Exception 异常
     */
    public void online(Connection connection, WebSocketSession session) throws Exception {
        cache.put(connection.getId(), new Entry(connection, session));
        operations.opsForValue().set(buildConnectionKey(connection.getId()), objectMapper.writeValueAsString(connection),
                webSocketConfig.getHeartbeatTimeout().plus(webSocketConfig.getCheckTimeout()));
    }

    /**
     * 是否在线
     *
     * @return 是否在线
     */
    public boolean isOnline(UserId userId) {
        return operations.opsForValue().get(buildConnectionKey(userId)) != null;
    }

    /**
     * 心跳
     *
     * @param connection 连接
     */
    public void heartbeat(Connection connection) throws IOException {
        Entry entry = cache.getIfPresent(connection.getId());
        if (entry != null) {
            entry.connection.setLastHeartbeat(Instant.now());
            cache.put(connection.getId(), entry);
            operations.expire(buildConnectionKey(connection.getId()),
                    webSocketConfig.getHeartbeatTimeout().plus(webSocketConfig.getCheckTimeout()));
            HeartbeatMessage message = buildHeartbeatMessage();
            final ObjectMapper cleanFieldObjectMapper = objectMapper.copy();
            cleanFieldObjectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
            String messageString = cleanFieldObjectMapper.writeValueAsString(message);
            entry.session().sendMessage(new TextMessage(messageString));
        }
    }

    /**
     * 获取心跳消息
     *
     * @return {@link HeartbeatMessage}
     */
    private HeartbeatMessage buildHeartbeatMessage() {
        HeartbeatMessage message = new HeartbeatMessage();
        message.setMsgType(MessageType.HEARTBEAT_MESSAGES)
                .setSendTime(Instant.now().toEpochMilli());
        message.setContent(new HeartbeatMessage.HeartbeatContent().setRaw("pong"));
        return message;
    }

    /**
     * 获取所有连接
     *
     * @return 连接列表
     */
    public List<Connection> getConnections() {
        return List.copyOf(cache.asMap().values().stream().map(Entry::connection).toList());
    }

    /**
     * 获取连接
     *
     * @param userId 连接ID
     * @return 连接
     */
    public Connection getConnection(UserId userId) {
        Entry entry = cache.getIfPresent(userId);
        return entry == null ? null : entry.connection;
    }

    /**
     * 获取会话
     *
     * @param userId 用户ID
     * @return 会话
     */
    public WebSocketSession getSession(UserId userId) {
        Entry entry = cache.getIfPresent(userId);
        return entry == null ? null : entry.session;
    }

    /**
     * 构建连接Key
     *
     * @param userId 用户ID
     * @return 连接Key
     */
    private String buildConnectionKey(UserId userId) {
        return CONNECTION_KEY + userId;
    }
}
