package com.nokia.hb.service;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * 网络套接字服务
 */
@EqualsAndHashCode(callSuper = true)
@Slf4j
@Service
@Data
@ConfigurationProperties("socket")
public class WebSocketService extends AbstractWebSocketHandler {
    /**
     * 会话映射
     */
    static final ConcurrentMap<String, WebSocketSession> SESSION_MAP = new ConcurrentHashMap<>();
    /**
     * ping时间map
     */
    static final ConcurrentMap<String, LocalDateTime> PING_MAP = new ConcurrentHashMap<>();
    /**
     * 心跳超时(s)
     */
    private Long heartbeatTimeout;
    private final SessionService sessionService;

    public WebSocketService(SessionService sessionService) {
        this.sessionService = sessionService;
    }

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        SESSION_MAP.put(session.getId(), session);
        Map<String, Object> attributes = session.getAttributes();
        String sessionId = (String) attributes.get("JSESSIONID");
        String username = (String) attributes.get("username");
        log.info("afterConnectionEstablished: {} -> {} -> {}", session.getId(), sessionId, username);
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        Map<String, Object> attributes = session.getAttributes();
        String sessionId = (String) attributes.get("JSESSIONID");
        String username = (String) attributes.get("username");
        log.error("handleTransportError: {} -> {} -> {} -> {}", session.getId(), sessionId, username,
                exception.getMessage(), exception);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
        Map<String, Object> attributes = session.getAttributes();
        String sessionId = (String) attributes.get("JSESSIONID");
        String username = (String) attributes.get("username");
        log.info("afterConnectionClosed: {}, {} -> {} -> {}", closeStatus, session.getId(), sessionId, username);
        sessionService.invalidate(username);
        SESSION_MAP.remove(session.getId());
    }
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        Map<String, Object> attributes = session.getAttributes();
        String sessionId = (String) attributes.get("JSESSIONID");
        String username = (String) attributes.get("username");
        log.info("handleTextMessage: {} -> {} -> {} -> {}", session.getId(), sessionId, username,
                message.getPayload());
    }

    @Override
    protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
        Map<String, Object> attributes = session.getAttributes();
        String sessionId = (String) attributes.get("JSESSIONID");
        String username = (String) attributes.get("username");
        log.info("handleBinaryMessage: {} -> {} -> {} -> {}", session.getId(), sessionId, username,
                message.getPayload());
    }

    @Override
    protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
        Map<String, Object> attributes = session.getAttributes();
        String sessionId = (String) attributes.get("JSESSIONID");
        String username = (String) attributes.get("username");
        log.info("handlePongMessage: {} -> {} -> {} -> {}", session.getId(), sessionId, username,
                message.getPayload());
        PING_MAP.put(session.getId(), LocalDateTime.now());
    }

    @Scheduled(fixedDelay = 5000)
    public void ping() {
        for (Map.Entry<String, WebSocketSession> entry : SESSION_MAP.entrySet()) {
            String k = entry.getKey();
            WebSocketSession v = entry.getValue();
            Map<String, Object> attributes = v.getAttributes();
            String sessionId = (String) attributes.get("JSESSIONID");
            String username = (String) attributes.get("username");
            PING_MAP.putIfAbsent(k, LocalDateTime.now());
            // 心跳超时断开连接
            if (LocalDateTime.now().minusSeconds(heartbeatTimeout).compareTo(PING_MAP.get(k)) > 0) {
                log.warn("超过{}s未收到pong: {} -> {} -> {}", heartbeatTimeout, v.getId(), sessionId, username);
                try {
                    v.close();
                    PING_MAP.remove(k);
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
                continue;
            }
            // 发送ping
            try {
                log.info("ping {} -> {} -> {}", v.getId(), sessionId, username);
                v.sendMessage(new PingMessage());
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }
    }
}
