package com.tsi.comm.server.controller;

import com.tsi.comm.server.config.ApplicationConfig;
import com.tsi.comm.server.config.TraceConfig;
import com.tsi.comm.server.config.TsiCvimServerConfig;
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.TsiCvimPacketProcess;
import com.tsi.comm.server.process.packet.TsiCvimPacketWorker;
import com.tsi.comm.server.repository.TsiAlarmManager;
import com.tsi.comm.server.repository.TsiNodeManager;
import com.tsi.comm.server.repository.TsiSessionManager;
import com.tsi.comm.server.vo.TsiNodeVo;
import com.tsi.comm.server.xnet.NettyUtils;
import com.tsi.common.utils.TimeUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

@RestController
@RequiredArgsConstructor
@RequestMapping("")
public class TsiCommServerRestController {

    private final ApplicationConfig config;
    private final TsiCvimServerConfig serverConfig;
    private final TraceConfig traceConfig;
    private final TsiNodeManager nodeManager;
    private final TsiSessionManager sessionManager;
    private final TsiAlarmManager alarmManager;

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

    private final String sep = System.lineSeparator();
    private final String heading = "--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------";

    private StringBuilder getCommonHead() {
        StringBuilder sb = new StringBuilder();
        sb.append(heading).append(sep);
        sb.append(" UTIC TSI Wireless Communication Server").append(sep);
        sb.append(String.format(" Process Id: %s, Server Id: %d, Binding: %d, %s, %s",
                this.config.getProcessId(), this.serverConfig.getServerId(), this.serverConfig.getBindingPort(),
                this.config.getBootingTime(), TimeUtils.now())).append(sep);
        sb.append(String.format(" Workers(Packet/Logging/Dbms): %d/%d/%d",
                this.serverConfig.getPacketWorkers(), this.serverConfig.getLoggingWorkers(), this.serverConfig.getDbmsWorkers())).append(sep);
        sb.append(heading).append(sep);
        return sb;
    }

    @GetMapping(value = "/info", produces = {"application/json; charset=utf8"})
    public String info() {
        int nodeCount = this.nodeManager.getTsiNodeVoMap().size();
        return infoNode(0, nodeCount, true);
    }

    // http://localhost:9871/info-page?page=0&size=100
    @GetMapping(value = "/info-page", produces = {"application/json; charset=utf8"})
    public String info(
            @RequestParam(value = "page", defaultValue = "0") int page,
            @RequestParam(value = "size", defaultValue = "100") int size) {
        return infoNode(page, size, false);

    }

    private String infoNode(int page, int size, boolean all) {

        StringBuilder sb = getCommonHead();
        int nodeCount = this.nodeManager.getTsiNodeVoMap().size();

        List<TsiNodeVo> allNodes = this.nodeManager.getTsiNodeVoMap().values().stream()
                .sorted(Comparator.comparing(TsiNodeVo::getNodeId)) // 일관된 순서를 위해 정렬
                .collect(Collectors.toList());

        int totalNodes = allNodes.size();
        int totalPages = (int) Math.ceil((double) totalNodes / size);

        if (!all) {
            sb.append(String.format(" Report Node Sessions: %d Nodes. (Page %d / %d)", totalNodes, page + 1, totalPages)).append(sep);
        }
        sb.append(String.format(" Report Node Sessions: %d Nodes. A-INTCA(AddNode-Install/SendNode/SendTest/SendCvim)", nodeCount)).append(sep);

        List<TsiNodeVo> pagedNodes = allNodes.stream()
                .skip((long) page * size)
                .limit(size)
                .collect(Collectors.toList());

        int seqStart = page * size + 1;
        //  CRC/PACKET
        //false/false
        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"));

        sb.append(String.format("   SEQ[U] %10s %4s %7s %19s %19s %9s %6s %19s  IP Address       Signal AvgTm(us)  Dropped/Overlapping Queue(P/L/D)",
                "Node ID", "A-INTC", "Connect", "Connect Time", "Disconnect Time", "Connected", "Closed", "Last-Recv-Time")).append(sep);
        sb.append(heading).append(sep);

//        List<TsiNodeVo> nodesSnapshot = new ArrayList<>(this.nodeManager.getTsiNodeVoMap().values());
//        for (TsiNodeVo node : nodesSnapshot) {
//        }

        for (TsiNodeVo node : pagedNodes) {
            String check = (node.isAddNodeEnabled() ? "Y-" : "N-");
            check = check + (node.isInstalled() ? "Y" : "N");
            check = check + (node.isSendNode() ? "Y" : "N");
            check = check + (node.isSendTest() ? "Y" : "N");
            check = check + (node.isSendCvim() ? "Y" : "N");
            String unknownNode = "   ";
            if (node.isRegistered()) {
                registered++;
            }
            else {
                unknown++;
                unknownNode = "***";
            }

            long avgTime = node.getSigCount() == 0 ? 0 : node.getAverageProcessingTimeMicros();
            String connect;
            String info;
            if (node.isConnect()) {
                connected++;
                connect = "Y";
                info = NettyUtils.getRemoteIpAddress(node.getChannel());
            }
            else {
                connect = "N";
                //info = "---.---.---.---";
                info = node.getIpAddr();
                avgTime = 0;
            }

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

            sb.append(String.format(" %5d%3s %10s %4s %7s %19s %19s %9d %6d %19s  %-15.15s  %6d %9d  %-19s %d/%d/%d",
                    seqStart++, unknownNode, node.getKey(), check, connect, connectTm, disconnectTm, node.getConnectCount().get(),
                    node.getDisconnectCount().get(), lastCommTm, info, //node.getIpAddr(), //info,
                    node.getSigCount(), avgTime,
                    String.format("%d/%d", node.getDroppedPacketCount(), node.getOverlappingPacketCount()),
                    node.getPktQIdx(), node.getLogQIdx(), node.getDbmsQIdx())).append(sep);
        }
        sb.append(heading).append(sep);
        sb.append(String.format(" Total Nodes: %d EA, Registered: %d EA, Unknown: %d EA, Connected: %d EA", nodeCount, registered, unknown, connected)).append(sep);
        sb.append(String.format(" Unknown Nodes: %d EA, Unknown Node Buffers: %d EA, Unknown Ip Address: %d EA, Duplication Connect: %d EA",
                this.alarmManager.getUnknownNodeCount(), this.alarmManager.getUnknownNodeBufCount(),
                this.alarmManager.getUnknownIpAddrCount(), this.alarmManager.getDupConnectCount())).append(sep);
//        sb.append(heading).append(sep);
        sb.append(String.format(" Channel: %d EA, Session: %d EA", this.sessionManager.getChannelCount(), this.sessionManager.getCount())).append(sep);
        sb.append(String.format(" TRACE DUMP: %s", this.traceConfig.getCurrentDumpNodeIds().toString())).append(sep);
        sb.append(String.format(" TRACE TCP DUMP: %s", this.traceConfig.getCurrentTcpDumpIps().toString())).append(sep);
        sb.append(heading).append(sep);

        return sb.toString();
    }

    @GetMapping(value = "/info/packet", produces = {"application/json; charset=utf8"})
    public String infoPacket() {
        StringBuilder sb = getCommonHead();

        sb.append(" Packet Worker Queue Information.").append(sep);

        List<AbstractTsiCvimWorker> processWorkerList = this.packetProcess.getWorkerList();
        List<TsiCvimPacketWorker> packetWorkers = processWorkerList.stream()
                .filter(worker -> worker instanceof TsiCvimPacketWorker)
                .map(worker -> (TsiCvimPacketWorker) worker)
                .collect(Collectors.toList());

        int jobs = 0;
        for (TsiCvimPacketWorker worker : packetWorkers) {
            worker.setJobs(0);
        }
        for (Map.Entry<Long, TsiNodeVo> obj : this.nodeManager.getTsiNodeVoMap().entrySet()) {
            final TsiNodeVo node = obj.getValue();
            if (node.isConnect()) {
                jobs++;
                int pktIdx = node.getPktQIdx();
                if (pktIdx >= 0 && pktIdx < packetWorkers.size()) {
                    packetWorkers.get(pktIdx).setJobs(packetWorkers.get(pktIdx).getJobs()+1);
                }
            }
        }
        sb.append(String.format(" Packet Worker: %d EA, QSize: %d EA.", processWorkerList.size(), this.packetProcess.getQSize())).append(sep);
        sb.append(heading).append(sep);

        sb.append(" Worker-----   Jobs  Total  Used Remain  Process Average Time     Process Count").append(sep);
        sb.append(heading).append(sep);
        int ii = 1;
        for (TsiCvimPacketWorker worker : packetWorkers) {
            sb.append(String.format(" Worker(%3d)  %5d  %5d %5d  %5d  %s  %d", ii++,
                    worker.getJobs(), worker.getQSize(),
                    worker.getDATA_QUEUE().size(), worker.getDATA_QUEUE().remainingCapacity(),
                    TimeUtils.elapsedTimeStr2(worker.getAvgTime()), worker.getProcessedCount())).append(sep);
        }
        sb.append(heading).append(sep);
        sb.append(String.format(" Total Jobs: %d EA.", jobs)).append(sep);
        sb.append(heading).append(sep);
        return sb.toString();
    }

    @GetMapping(value = "/info/logging", produces = {"application/json; charset=utf8"})
    public String infoLogging() {
        StringBuilder sb = getCommonHead();

        sb.append(" Logging Worker Queue Information.").append(sep);

        List<AbstractTsiCvimWorker> loggingWorkerList = this.loggingProcess.getWorkerList();
        List<TsiCvimLoggingWorker> loggingWorkers = loggingWorkerList.stream()
                .filter(worker -> worker instanceof TsiCvimLoggingWorker)
                .map(worker -> (TsiCvimLoggingWorker) worker)
                .collect(Collectors.toList());

        int jobs = 0;
        for (AbstractTsiCvimWorker worker : loggingWorkerList) {
            worker.setJobs(0);
        }
        for (Map.Entry<Long, TsiNodeVo> obj : this.nodeManager.getTsiNodeVoMap().entrySet()) {
            final TsiNodeVo node = obj.getValue();
            if (node.isConnect()) {
                jobs++;
                int logIdx = node.getLogQIdx();
                if (logIdx >= 0 && logIdx < loggingWorkers.size()) {
                    loggingWorkers.get(logIdx).setJobs(loggingWorkers.get(logIdx).getJobs()+1);
                }
            }
        }
        sb.append(String.format(" Logging Worker: %d EA, QSize: %d EA.", loggingWorkerList.size(), this.loggingProcess.getQSize())).append(sep);
        sb.append(heading).append(sep);

        sb.append(" Worker-----   Jobs  Total  Used Remain  Process Average Time     Process Count").append(sep);
        sb.append(heading).append(sep);
        int ii = 1;
        for (TsiCvimLoggingWorker worker : loggingWorkers) {
            sb.append(String.format(" Worker(%3d)  %5d  %5d %5d  %5d  %s  %d", ii++,
                    worker.getJobs(), worker.getQSize(),
                    worker.getDATA_QUEUE().size(), worker.getDATA_QUEUE().remainingCapacity(),
                    TimeUtils.elapsedTimeStr2(worker.getAvgTime()), worker.getProcessedCount())).append(sep);
        }
        sb.append(heading).append(sep);
        sb.append(String.format(" Total Jobs: %d EA.", jobs)).append(sep);
        sb.append(heading).append(sep);
        return sb.toString();
    }

    @GetMapping(value = "/info/dbms", produces = {"application/json; charset=utf8"})
    public String infoDbms() {
        StringBuilder sb = getCommonHead();

        sb.append(" Dbms Worker Queue Information.").append(sep);

        List<AbstractTsiCvimWorker> dbmsWorkerList = this.dbmsProcess.getWorkerList();
        List<TsiCvimDbmsWorker> dbmsWorkers = dbmsWorkerList.stream()
                .filter(worker -> worker instanceof TsiCvimDbmsWorker)
                .map(worker -> (TsiCvimDbmsWorker) worker)
                .collect(Collectors.toList());

        int jobs = 0;
        for (AbstractTsiCvimWorker worker : dbmsWorkerList) {
            worker.setJobs(0);
        }
        for (Map.Entry<Long, TsiNodeVo> obj : this.nodeManager.getTsiNodeVoMap().entrySet()) {
            final TsiNodeVo node = obj.getValue();
            if (node.isConnect()) {
                jobs++;
                int dbmsIdx = node.getDbmsQIdx();
                if (dbmsIdx >= 0 && dbmsIdx < dbmsWorkerList.size()) {
                    dbmsWorkerList.get(dbmsIdx).setJobs(dbmsWorkerList.get(dbmsIdx).getJobs()+1);
                }
            }
        }
        sb.append(String.format(" Dbms Worker: %d EA, QSize: %d EA.", dbmsWorkerList.size(), this.dbmsProcess.getQSize())).append(sep);
        sb.append(heading).append(sep);

        sb.append(" Worker-----   Jobs  Total  Used Remain  Process Average Time     Process Count").append(sep);
        sb.append(heading).append(sep);
        int ii = 1;
        for (TsiCvimDbmsWorker worker : dbmsWorkers) {
            sb.append(String.format(" Worker(%3d)  %5d  %5d %5d  %5d  %s  %d", ii++,
                    worker.getJobs(), worker.getQSize(),
                    worker.getDATA_QUEUE().size(), worker.getDATA_QUEUE().remainingCapacity(),
                    TimeUtils.elapsedTimeStr2(worker.getAvgTime()), worker.getProcessedCount())).append(sep);
        }
        sb.append(heading).append(sep);
        sb.append(String.format(" Total Jobs: %d EA.", jobs)).append(sep);
        sb.append(heading).append(sep);
        return sb.toString();
    }

    @GetMapping(value = "/info/alarm", produces = {"application/json; charset=utf8"})
    public String infoAlarm() {
        StringBuilder sb = getCommonHead();

//        sb.append(sep).append(" System Alarm Information.").append(sep);
        sb.append(heading).append(sep).append(sep);
        sb.append("***Un Registered Node Information.").append(sep);
        sb.append(heading).append(sep);
        sb.append("   SEQ Node ID     Remote-Address").append(sep);
        sb.append(heading).append(sep);
        int ii = 1;
        for (Map.Entry<Long, TsiNodeVo> obj : this.nodeManager.getTsiNodeVoMap().entrySet()) {
            final TsiNodeVo node = obj.getValue();
            if (!node.isRegistered()) {
                sb.append(String.format(" %5d %10s  %s", ii++, node.getKey(), node.getIpAddr())).append(sep);
            }
        }
        sb.append(heading).append(sep).append(sep);

        ii = 1;
        sb.append("**Node Id Zero Information.").append(sep);
        sb.append(heading).append(sep);
        sb.append("   SEQ Remote-Address").append(sep);
        sb.append(heading).append(sep);
        Set<String> UNKNOWN_NODE_BUF_SET = this.alarmManager.getUNKNOWN_NODE_BUF_SET();
        for (String ipAddr : UNKNOWN_NODE_BUF_SET) {
            sb.append(String.format(" %5d %s", ii++, ipAddr)).append(sep);
        }
        sb.append(heading).append(sep).append(sep);

        ii = 1;
        sb.append("*Duplicated Connect Node Information.").append(sep);
        sb.append(heading).append(sep);
        sb.append("   SEQ Information").append(sep);
        sb.append(heading).append(sep);
        Set<String> DUP_CONNECT_INFO_SET = this.alarmManager.getDUP_CONNECT_INFO_SET();
        for (String info : DUP_CONNECT_INFO_SET) {
            sb.append(String.format(" %5d %s", ii++, info)).append(sep);
        }
        sb.append(heading).append(sep).append(sep);

        ii = 1;
        sb.append("*Signal Phase count zero Node Information.").append(sep);
        sb.append(heading).append(sep);
        sb.append("   SEQ Information").append(sep);
        sb.append(heading).append(sep);
        for (Map.Entry<Long, TsiNodeVo> obj : this.nodeManager.getTsiNodeVoMap().entrySet()) {
            final TsiNodeVo node = obj.getValue();
            if (node.isConnect() && node.getSigCount() == 0) {
                sb.append(String.format(" %5d %d", ii++, node.getNodeId())).append(sep);
            }
        }
        sb.append(heading).append(sep).append(sep);

        ii = 1;
        sb.append("*CRC Error Node Information.").append(sep);
        sb.append(heading).append(sep);
        sb.append("   SEQ Information").append(sep);
        sb.append(heading).append(sep);
        Set<String> CRC_ERROR_INFO_SET = this.alarmManager.getCRC_ERROR_INFO_SET();
        for (String info : CRC_ERROR_INFO_SET) {
            sb.append(String.format(" %5d %s", ii++, info)).append(sep);
        }
        sb.append(heading).append(sep).append(sep);

        ii = 1;
        sb.append("*PACKET Error Node Information.").append(sep);
        sb.append(heading).append(sep);
        sb.append("   SEQ Information").append(sep);
        sb.append(heading).append(sep);
        Set<String> PACKET_ERROR_INFO_SET = this.alarmManager.getPACKET_ERROR_INFO_SET();
        for (String info : PACKET_ERROR_INFO_SET) {
            sb.append(String.format(" %5d %s", ii++, info)).append(sep);
        }

        sb.append(heading).append(sep);
        return sb.toString();
    }

}
