package com.tsi.comm.server.repository;

import com.tsi.app.common.utils.TimeUtils;
import com.tsi.app.common.xnet.NettyUtils;
import com.tsi.comm.server.mybatis.vo.AbstractDbmsVo;
import com.tsi.comm.server.mybatis.vo.AlarmOccrVo;
import com.tsi.comm.server.process.AbstractTsiCvimWorker;
import com.tsi.comm.server.process.dbms.TsiCvimDbmsProcess;
import com.tsi.comm.server.process.dbms.TsiCvimDbmsWorker;
import com.tsi.comm.server.process.logging.TsiCvimLoggingProcess;
import com.tsi.comm.server.process.logging.TsiCvimLoggingWorker;
import com.tsi.comm.server.process.packet.TsiChannelSession;
import com.tsi.comm.server.process.packet.TsiCvimPacketProcess;
import com.tsi.comm.server.process.packet.TsiCvimPacketWorker;
import com.tsi.comm.server.vo.TsiAlarmConfigVo;
import com.tsi.comm.server.vo.TsiNodeVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@RequiredArgsConstructor
@Component
public class TsiReportManager {

    private final TsiNodeManager nodeManager;
    private final TsiCvimDbmsProcess dbmsProcess;
    private final TsiCvimLoggingProcess loggingProcess;
    private final TsiCvimPacketProcess packetProcess;

    public void reportNodeSessions() {
        final String fileName = "report";
        MDC.put("filename", fileName);

        int nodeCount = this.nodeManager.getTsiNodeVoMap().size();
        log.info("================================================================================================================");
        log.info("Report Node Sessions: {} Nodes.", nodeCount);

        int registered = 0;
        int unknown = 0;
        int connected = 0;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        sdf.setTimeZone(java.util.TimeZone.getTimeZone("GMT+9"));

        log.info("{}", String.format("%10s %4s %7s %19s %19s %9s %6s %19s  Remote-Address", "ID", "INTC", "Connect", "Connect Time", "Disconnect Time", "Connected", "Closed", "Last-Recv-Time"));
        log.info("----------------------------------------------------------------------------------------------------------------");

        for (Map.Entry<Long, TsiNodeVo> obj : this.nodeManager.getTsiNodeVoMap().entrySet()) {
            TsiNodeVo node = obj.getValue();
            String check = (node.isInstalled() ? "Y" : "N");
            check = check + (node.isSendNode() ? "Y" : "N");
            check = check + (node.isSendTest() ? "Y" : "N");
            check = check + (node.isSendCvim() ? "Y" : "N");
            if (node.isRegistered()) {
                registered++;
            }
            else {
                unknown++;
            }

            String connect;
            String info;
            if (node.isConnect()) {
                connected++;
                connect = "Y";
                info = NettyUtils.getRemoteAddress(node.getChannel());
            }
            else {
                connect = "N";
                info = "---";
            }

            String connectTm = sdf.format(new Date(node.getConnectTm()));
            String disconnectTm = sdf.format(new Date(node.getDisconnectTm()));
            String lastCommTm = sdf.format(new Date(node.getLastCommTm()));

            log.info("{}", String.format("%10s %4s %7s %19s %19s %9d %6d %19s  %s", node.getKey(), check, connect, connectTm, disconnectTm, node.getConnectCount().get(), node.getDisconnectCount().get(), lastCommTm, info));
        }
        log.info("----------------------------------------------------------------------------------------------------------------");
        log.info("Total Nodes: {} EA, Registered: {} EA, Unknown: {} EA, Connected: {} EA", nodeCount, registered, unknown, connected);
        log.info("==============================================");

        MDC.clear();
    }

    public void checkSessionTimeout(long timeout) {
        final String fileName = "timeout";

        String connectTm;
        String lastCommTm;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        sdf.setTimeZone(java.util.TimeZone.getTimeZone("GMT+9"));
        long currTm = System.currentTimeMillis();
        for (Map.Entry<Long, TsiNodeVo> obj : this.nodeManager.getTsiNodeVoMap().entrySet()) {
            TsiNodeVo node = obj.getValue();
            if (node.isConnect()) {
                if ((currTm - node.getLastCommTm()) > timeout) {
                    try {
                        String ipAddress = node.getIpAddr();
                        if (node.getChannel() != null) {
                            ipAddress = NettyUtils.getRemoteIpAddress(node.getChannel());
                        }

                        AlarmOccrVo alarm = new AlarmOccrVo(AbstractDbmsVo.DBMS_ALARM_OCCR_HS);
                        alarm.setAlarmCode(TsiAlarmConfigVo.COMM_02);
                        alarm.setAlarmTarget(node.getKey());
                        alarm.setAlarmValue(ipAddress);
                        this.dbmsProcess.add(alarm, (int) Thread.currentThread().getId());

                        connectTm = sdf.format(new Date(node.getConnectTm()));
                        lastCommTm = sdf.format(new Date(node.getLastCommTm()));

                        MDC.put("filename", fileName);

                        log.info("Node: {}, Recv Timeout: Connect: {}, LastCommTime: {}, timeout: {}",
                                node.getKey(), connectTm, lastCommTm, timeout);

                        MDC.clear();

                        TsiChannelSession.sessionTimeout(node, NettyUtils.getRemoteIpAddress(node.getChannel()));

                        if (node.getChannel() != null) {
                            node.getChannel().disconnect();
                            node.getChannel().close();
                        }
                    }
                    catch (Exception e) {
                        log.error("checkSessionTimeout: {}", e.getMessage());
                    }
                }
            }
        }
    }

    public void reportQueueInfo(boolean isAlert) {
        final String fileName = "queue";
        MDC.put("filename", fileName);

        boolean isTail = false;
        int remainingCapacity = 0;

        if (!isAlert) {
            log.info("================================================================================================================");
            log.info("Worker Queue Information.");
        }
        List<AbstractTsiCvimWorker> processWorkerList = this.packetProcess.getWorkerList();
        remainingCapacity = isAlert ? 100 : this.packetProcess.getQSize();
        if (!isAlert) {
            log.info("-----  Packet Worker: {} EA, QSize: {} EA.", processWorkerList.size(), this.packetProcess.getQSize());
        }
        List<TsiCvimPacketWorker> packetWorkers = processWorkerList.stream()
                .filter(worker -> worker instanceof TsiCvimPacketWorker)
                .map(worker -> (TsiCvimPacketWorker) worker)
                .collect(Collectors.toList());
        for (TsiCvimPacketWorker worker : packetWorkers) {
            if (worker.getDATA_QUEUE().remainingCapacity() <= remainingCapacity) {
                log.info("PacketWorker({}), Total/Size/Remain: {}/{}/{}, Average: {}",
                        worker.getIdx(),
                        worker.getQSize(), worker.getDATA_QUEUE().size(), worker.getDATA_QUEUE().remainingCapacity(),
                        TimeUtils.elapsedTimeStr(worker.getAvgTime()));
                isTail = true;
            }
        }

        List<AbstractTsiCvimWorker> dbmsWorkerList = this.dbmsProcess.getWorkerList();
        remainingCapacity = isAlert ? 100 : this.dbmsProcess.getQSize();
        if (!isAlert) {
            log.info("-----    Dbms Worker: {} EA, QSize: {} EA.", dbmsWorkerList.size(), this.dbmsProcess.getQSize());
        }
        List<TsiCvimDbmsWorker> dbmsWorkers = dbmsWorkerList.stream()
                .filter(worker -> worker instanceof TsiCvimDbmsWorker)
                .map(worker -> (TsiCvimDbmsWorker) worker)
                .collect(Collectors.toList());
        for (TsiCvimDbmsWorker worker : dbmsWorkers) {
            if (worker.getDATA_QUEUE().remainingCapacity() <= remainingCapacity) {
                log.info("DbmsWorker({}), Total/Size/Remain: {}/{}/{}, Average: {}",
                        worker.getIdx(),
                        worker.getQSize(), worker.getDATA_QUEUE().size(), worker.getDATA_QUEUE().remainingCapacity(),
                        TimeUtils.elapsedTimeStr(worker.getAvgTime()));
                isTail = true;
            }
        }

        List<AbstractTsiCvimWorker> loggingWorkerList = this.loggingProcess.getWorkerList();
        remainingCapacity = isAlert ? 100 : this.loggingProcess.getQSize();
        if (!isAlert) {
            log.info("----- Logging Worker: {} EA, QSize: {} EA.", loggingWorkerList.size(), this.loggingProcess.getQSize());
        }
        List<TsiCvimLoggingWorker> loggingWorkers = loggingWorkerList.stream()
                .filter(worker -> worker instanceof TsiCvimLoggingWorker)
                .map(worker -> (TsiCvimLoggingWorker) worker)
                .collect(Collectors.toList());
        for (TsiCvimLoggingWorker worker : loggingWorkers) {
            if (worker.getDATA_QUEUE().remainingCapacity() <= remainingCapacity) {
                log.info("LoggingWorker({}), Total/Size/Remain: {}/{}/{}, Average: {}",
                        worker.getIdx(),
                        worker.getQSize(), worker.getDATA_QUEUE().size(), worker.getDATA_QUEUE().remainingCapacity(),
                        TimeUtils.elapsedTimeStr(worker.getAvgTime()));
                isTail = true;
            }
        }
        if (isTail) {
            log.info("================================================================================================================");
        }
        MDC.clear();
    }

}
