package com.tsi.comm.server.process.logging;

import com.tsi.app.common.app.AppUtils;
import com.tsi.app.common.utils.TimeUtils;
import com.tsi.comm.server.mongo.dto.TcsNodeStatus;
import com.tsi.comm.server.mongo.repository.TsiNodeStatusRepository;
import com.tsi.comm.server.process.AbstractTsiCvimWorker;
import com.tsi.comm.server.protocol.AbstractTsiPacket;
import com.tsi.comm.server.protocol.TsiCpuPacket;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.LinkedBlockingQueue;

@Slf4j
public class TsiCvimLoggingWorker extends AbstractTsiCvimWorker implements Runnable {

    private final LinkedBlockingQueue<AbstractTsiPacket> DATA_QUEUE;
    private TsiNodeStatusRepository tsiNodeStatusRepository;

    public TsiCvimLoggingWorker(int idx, int qSize) {
        this.idx = idx;
        this.qSize = qSize;
        this.DATA_QUEUE = new LinkedBlockingQueue<>(qSize);
        try {
            this.tsiNodeStatusRepository = (TsiNodeStatusRepository) AppUtils.getBean(TsiNodeStatusRepository.class);
        }
        catch(Exception e) {
            this.tsiNodeStatusRepository = null;
            log.error("MongoDB: {}", e.getMessage());
        }
    }

    @Override
    public void run() {
        log.info("{} Start. QSIZE: {}", Thread.currentThread().getName(), this.qSize);
        while (true) {
            try {
                AbstractTsiPacket packet = this.DATA_QUEUE.take();
                if (packet != null) {
                    process(packet);
                }
            }
            catch (Exception e) {
                log.error("Exception: {}", e.getMessage());
            }
        }
    }

    /*
     *  작업큐에 데이터 추가
     */
    public boolean add(Object object) {
        boolean offer = false;
        AbstractTsiPacket packet = (AbstractTsiPacket)object;
        try {
            //offer => full -> return
            //add   => full -> wait
            offer = this.DATA_QUEUE.offer(packet);
            if (!offer) {
                MDC.put("id", Long.toString(packet.getNodeId()));
                log.error("Logging Queue.offer: {}/{}, Queue Full: {} EA, {}, {}",
                        packet.getNodeId(), this.DATA_QUEUE.size(), this.qSize, TimeUtils.elapsedTime(packet.getRcv()), Thread.currentThread().getName());
                MDC.clear();
            }
        } catch (Exception e) {
            MDC.put("id", Long.toString(packet.getNodeId()));
            log.error("Logging Queue.offer: Exception: {}, {}, {}", packet.getNodeId(), Thread.currentThread().getName(), e.getMessage());
            MDC.clear();
        }
        return offer;
    }

    public void process(Object object) {
        AbstractTsiPacket packet = (AbstractTsiPacket)object;
        long add = packet.getAdd() - packet.getRcv();
        long pop = packet.getPop() - packet.getAdd();
        long par = packet.getPar() - packet.getPop();
        long kaf = packet.getEnd() - packet.getPar();
        long mongo = 0, jobTotal = 0;
        long job = packet.getEnd() - packet.getRcv();

        Date date = new java.util.Date(packet.getTimespec().times() * 1000L);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        sdf.setTimeZone(java.util.TimeZone.getTimeZone("GMT+9"));
        String collectTime = sdf.format(date);

        if (this.tsiNodeStatusRepository != null && (packet instanceof TsiCpuPacket)) {
            try {
                long start = System.nanoTime();
                TsiCpuPacket cpuPacket = (TsiCpuPacket) packet;
                TcsNodeStatus mongoStatus = cpuPacket.getNodeStatus();
                if (mongoStatus != null) {
                    //log.info("{}", mongoStatus.toString());
                    this.tsiNodeStatusRepository.save(mongoStatus);
                    mongo = System.nanoTime() - start;
                    packet.setEnd(System.nanoTime());
                    jobTotal = packet.getEnd() - packet.getRcv();
                }
            }
            catch (Exception e) {
                log.error("Node: {}, MongoDB parsing error.", packet.getNodeId());
            }
        }

        MDC.put("id", Long.toString(packet.getNodeId()));
//        log.info("{} Node: {},      Q-Add: {} {}", collectTime, packet.getNodeId(), TimeUtils.elapsedTimeStr(add), Thread.currentThread().getName());
//        log.info("{} Node: {},      Q-Pop: {} {}", collectTime, packet.getNodeId(), TimeUtils.elapsedTimeStr(pop), Thread.currentThread().getName());
//        log.info("{} Node: {},    Parsing: {} {}", collectTime, packet.getNodeId(), TimeUtils.elapsedTimeStr(par), Thread.currentThread().getName());
//        log.info("{} Node: {},      kafka: {} {}", collectTime, packet.getNodeId(), TimeUtils.elapsedTimeStr(kaf), Thread.currentThread().getName());
        log.info("{} Node: {},        Job: {} {}, {} bytes, Average {}",
                collectTime,
                packet.getNodeId(),
                TimeUtils.elapsedTimeStr(job),
                Thread.currentThread().getName(),
                packet.getPacketLength(),
                TimeUtils.elapsedTimeStr(packet.getAvg()));
        if (mongo > 0) {
            log.info("{} Node: {},    MongoDb: {} {}", collectTime, packet.getNodeId(), TimeUtils.elapsedTimeStr(mongo), Thread.currentThread().getName());
            log.info("{} Node: {},  Total Job: {} {}",
                    collectTime,
                    packet.getNodeId(),
                    TimeUtils.elapsedTimeStr(jobTotal),
                    Thread.currentThread().getName());
        }
//        log.info("{} Node: {}, ====", collectTime, packet.getNodeId());
        MDC.clear();
    }

    public void report() {
        log.info("Logging: Remain Q: {}, Average {}, {}", this.DATA_QUEUE.size(), TimeUtils.elapsedTimeStr(this.avgTime), Thread.currentThread().getName());
    }
}
