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

import cn.reghao.bnt.web.admin.model.vo.AccessLogInfo;
import cn.reghao.bnt.web.ws.config.WebSocketPath;
import cn.reghao.jutil.jdk.converter.DateTimeConverter;
import cn.reghao.jutil.jdk.web.log.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.ZoneOffset;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;

/**
 * @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, String> appMap = new HashMap<>();
    private final Map<String, WebSocketSession> pushSessionMap = new ConcurrentHashMap<>();
    private final Map<String, WebSocketSession> pullSessionMap = new ConcurrentHashMap<>();
    private final List<AppLog> appLogs = new ArrayList<>();

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

    public List<String> getApps() {
        return new ArrayList<>(appMap.values());
    }

    public List<String> getApp(String app) {
        return appMap.values().stream()
                .filter(appKey -> appKey.startsWith(app))
                .collect(Collectors.toList());
    }

    @Override
    public void afterConnectionEstablished(WebSocketSession webSocketSession) throws IOException {
        String sessionId = webSocketSession.getId();
        String query = webSocketSession.getUri().getQuery();
        Map<String, String> map = parseParams(query);
        String app = map.get("app");
        String host = map.get("host");
        if (app == null || host == null) {
            if (webSocketSession.isOpen()) {
                webSocketSession.close();
            }
        }

        String appKey = String.format("%s@%s", app, host);
        String path = webSocketSession.getUri().getPath();
        if (path.equals(WebSocketPath.wsPushLog)) {
            appMap.put(sessionId, appKey);
            pushSessionMap.put(appKey, webSocketSession);
        } else if (path.equals(WebSocketPath.wsPullLog)) {
            pullSessionMap.put(appKey, webSocketSession);
        }

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

    private void removeSession(WebSocketSession webSocketSession) {
        String sessionId = webSocketSession.getId();
        String appKey = appMap.get(sessionId);
        if (appKey == null) {
            return;
        }

        String path = webSocketSession.getUri().getPath();
        if (path.equals("/ws/log/push")) {
            pushSessionMap.remove(appKey);
        } else if (path.equals("/ws/log/pull")) {
            pullSessionMap.remove(appKey);
        }
    }

    public void sendAccessLog(AccessLogInfo accessLogInfo) {
        String app = "tnb";
        String host = "localhost";
        WebSocketSession pullSession = getPullSession(app, host);
        if (pullSession != null) {
            String jsonData = JsonConverter.objectToJson(accessLogInfo);
            WebSocketMessage<String> message1 = new TextMessage(jsonData);
            try {
                pullSession.sendMessage(message1);
            } catch (Exception e) {
                log.error("websocket error {}", e.getMessage());
                removeSession(pullSession);
            }
        }
    }

    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 {
            String appKey = appMap.get(webSocketSession.getId());
            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 String) {
                    log.info("{} -> {}", appKey, object);
                } else if (object instanceof AppLog) {
                    AppLog appLog = (AppLog) object;
                    String dateTimeStr = DateTimeConverter.format(appLog.getTimestamp());
                    appLogs.add(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 数据传输错误");
        removeSession(webSocketSession);
    }

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

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

        return null;
    }

    @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();
                }
            }
        }
    }
}
