package cn.reghao.devops.web.ws.handler;

import cn.reghao.devops.web.mgr.log.model.po.NginxLog;
import cn.reghao.jutil.jdk.converter.DateTimeConverter;
import cn.reghao.jutil.jdk.result.AppLog;
import cn.reghao.jutil.jdk.serializer.JdkSerializer;
import cn.reghao.jutil.jdk.serializer.JsonConverter;
import cn.reghao.jutil.jdk.thread.ThreadPoolWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.*;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;

/**
 * @author reghao
 * @date 2022-03-11 16:45:52
 */
@Slf4j
@Component
public class LogHandler implements WebSocketHandler {
    private final ExecutorService threadPool = ThreadPoolWrapper.threadPool("push-task", 1);
    private final Map<String, WebSocketSession> pullSessions = new ConcurrentHashMap<>();

    public LogHandler() {
        //threadPool.submit(new PushTask());
    }

    @Override
    public void afterConnectionEstablished(WebSocketSession webSocketSession) throws IOException {
        String path = webSocketSession.getUri().getPath();
        if (path.equals("/ws/log/push")) {
            String query = webSocketSession.getUri().getQuery();
            Map<String, String> map = parseParams(query);
            String app = map.get("app");
            String host = map.get("host");
            String key = String.format("%s-%s", app, host);
            //pushSessions.put(key, webSocketSession);
        } else if (path.equals("/ws/log/pull")) {
            String query = webSocketSession.getUri().getQuery();
            Map<String, String> map = parseParams(query);
            String app = map.get("app");
            String host = map.get("host");
            String key = String.format("%s-%s", app, host);
            putPullSession(app, host, webSocketSession);
        }

        log.info("WebSocket 建立连接");
    }

    public void pushAppLog(AppLog appLog) {
        String app = appLog.getApp();
        String host = appLog.getHost();
        WebSocketSession pullSession = getPullSession(app, host);
        if (pullSession != null) {
            String jsonData = JsonConverter.objectToJson(appLog);
            WebSocketMessage<String> message1 = new TextMessage(jsonData);
            try {
                pullSession.sendMessage(message1);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private Map<String, String> parseParams(String query) {
        String[] params = query.split("&");
        Map<String, String> map = new HashMap<>();
        for (String param : params) {
            String[] arr = param.split("=");
            map.put(arr[0], arr[1]);
        }

        return map;
    }

    Map<Long, Integer> ngxLogMap = new TreeMap<>();
    @Override
    public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage)
            throws IOException {
        try {
            if (webSocketMessage instanceof TextMessage) {
                String payload = (String) webSocketMessage.getPayload();
            } else if (webSocketMessage instanceof BinaryMessage) {
                log.info("接收到 WebSocket 二进制消息");
                BinaryMessage binaryMessage = (BinaryMessage) webSocketMessage;
                Object object = JdkSerializer.deserialize(binaryMessage.getPayload().array());
                if (object instanceof AppLog) {
                    AppLog appLog = (AppLog) object;
                    String dateTimeStr = DateTimeConverter.format(appLog.getTimestamp());
                    pushAppLog(appLog);
                } else if (object instanceof NginxLog) {
                    NginxLog nginxLog = (NginxLog) object;
                    String date = nginxLog.getTimeIso8601();
                    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss+08:00")
                            .withZone(ZoneId.of("UTC"));
                    LocalDateTime localDateTime = LocalDateTime.parse(date, formatter);
                    Long timestamp = localDateTime.toEpochSecond(ZoneOffset.of("+8"));
                    Long key = timestamp;
                    Integer count = ngxLogMap.get(key);
                    if (count == null) {
                        ngxLogMap.put(key, 1);
                    } else {
                        int count1 = ngxLogMap.get(key) + 1;
                        ngxLogMap.put(key, count1);
                    }
                }
            } else if (webSocketMessage instanceof PingMessage) {
                log.info("接收到 WebSocket PingMessage");
            } else if (webSocketMessage instanceof PongMessage) {
                log.info("接收到 WebSocket PongMessage");
            } else {
                log.error("接收到未知类型的 WebSocket 消息");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) {
        log.error("WebSocket 数据传输错误");
        String sessionId = webSocketSession.getId();
        removePullSession(sessionId);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) {
        log.info("WebSocket 断开连接");
        String sessionId = webSocketSession.getId();
        removePullSession(sessionId);
    }

    private void putPullSession(String app, String host, WebSocketSession session) {
        String sessionId = session.getId();
        String key = String.format("%s-%s-%s", sessionId, app, host);
        pullSessions.put(key, session);
    }

    private WebSocketSession getPullSession(String app, String host) {
        String suffix = String.format("%s-%s", app, host);
        Set<String> keys = pullSessions.keySet();
        for (String key : keys) {
            if (key.endsWith(suffix)) {
                return pullSessions.get(key);
            }
        }

        return null;
    }

    private void removePullSession(String sessionId) {
        Set<String> keys = pullSessions.keySet();
        keys.forEach(key -> {
            if (key.startsWith(sessionId)) {
                pullSessions.remove(key);
            }
        });
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }

    /**
     * NginxLog 在前端 echarts 中的可视化
     *
     * @param
     * @return
     * @date 2023-12-01 17:41:07
     */
    class PushTask implements Runnable {
        @Override
        public void run() {
            while (!Thread.interrupted()) {
                try {
                    if (ngxLogMap.size() < 3) {
                        Thread.sleep(10_000);
                        continue;
                    }

                    LocalDateTime localDateTime = LocalDateTime.now();
                    Long baseKey = localDateTime.toEpochSecond(ZoneOffset.of("+8"));

                    List<String> xList = new ArrayList<>();
                    List<Integer> yList = new ArrayList<>();
                    Set<Long> keys = new HashSet<>();
                    for (Long key : ngxLogMap.keySet()) {
                        if (key < baseKey) {
                            xList.add(DateTimeConverter.format(key*1000).split(" ")[1]);
                            yList.add(ngxLogMap.get(key));
                            keys.add(key);
                        }
                    }

                    keys.forEach(ngxLogMap::remove);
                    keys.clear();
                    List results = new ArrayList();
                    results.add(xList.toArray());
                    results.add(yList.toArray());

                    WebSocketSession webSocketSession = getPullSession("admin-service", "172.16.90.200");
                    TextMessage textMessage = new TextMessage(JsonConverter.objectToJson(results));
                    webSocketSession.sendMessage(textMessage);
                    Thread.sleep(10_000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
