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

import com.tsi.comm.server.protocol.enums.eOpCode;
import com.tsi.comm.server.config.TraceConfig;
import com.tsi.comm.server.config.TsiCvimServerConfig;
import com.tsi.comm.server.kafka.KafkaProducerService;
import com.tsi.comm.server.process.AbstractTsiCvimWorker;
import com.tsi.comm.server.process.logging.TsiCvimLoggingProcess;
import com.tsi.comm.server.protocol.AbstractTsiPacket;
import com.tsi.comm.server.protocol.TsiCpuDisconnected;
import com.tsi.comm.server.protocol.TsiCpuPacket;
import com.tsi.comm.server.vo.TsiNodeVo;
import com.tsi.common.spring.SpringUtils;
import com.tsi.common.utils.HexString;
import com.tsi.common.utils.TimeUtils;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

@Slf4j
@Getter
public class TsiCvimPacketWorker extends AbstractTsiCvimWorker implements Runnable {

    private final LinkedBlockingQueue<AbstractTsiPacket> DATA_QUEUE;

    private final KafkaProducerService kafkaProducer;
    private final TsiCvimLoggingProcess loggingService;
    private final TraceConfig traceConfig;
    private final TsiCvimServerConfig cvimServerConfig;

    public TsiCvimPacketWorker(int idx, int qSize, KafkaProducerService kafkaProducer) {
        this.idx = idx;
        this.qSize = qSize;
        this.kafkaProducer = kafkaProducer;
        this.DATA_QUEUE = new LinkedBlockingQueue<>(qSize);
        this.loggingService = SpringUtils.getBean(TsiCvimLoggingProcess.class);
        this.traceConfig = SpringUtils.getBean(TraceConfig.class);
        this.cvimServerConfig = SpringUtils.getBean(TsiCvimServerConfig.class);
    }

    @Override
    public void run() {
        log.info("PacketWorker({}): {} Start. QSIZE: {}", this.idx, Thread.currentThread().getName(), this.qSize);
        try {
            while (!Thread.currentThread().isInterrupted()) {
                Object packet = null;
                try {
                    packet = this.DATA_QUEUE.poll(1, TimeUnit.SECONDS); // 인터럽트 감지 가능
                    if (packet != null) {
                        process(packet);
                    } else {
                        Thread.yield(); // 대기 중 CPU 양보
                    }
                } catch (InterruptedException ie) {
                    log.warn("PacketWorker({}): {} Interrupted. Exiting...", this.idx, Thread.currentThread().getName());
                    Thread.currentThread().interrupt(); // 상태 복구
                    break;
                } catch (Exception e) {
                    if (packet != null) {
                        TsiCpuPacket cpuPacket = (TsiCpuPacket) packet;
                        TsiNodeVo nodeVo = (TsiNodeVo)cpuPacket.getObj();
                        if (nodeVo != null) {
                            log.warn("PacketWorker({}): {} Node: {}, Network Conn: {}", this.idx, Thread.currentThread().getName(), nodeVo.getKey(), nodeVo.isConnect());
                        }
                    }
                    else {
                        log.error("PacketWorker({}): {} Exception: {}", this.idx, Thread.currentThread().getName(), e.getMessage());
                    }
                }
            }
        } finally {
            log.info("PacketWorker({}): {} Stopped.", this.idx, Thread.currentThread().getName());
        }
    }

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

    public void process(Object object) {
        AbstractTsiPacket packet = (AbstractTsiPacket)object;
        TsiCpuPacket cpuPacket = (TsiCpuPacket)packet;
        TsiNodeVo nodeVo = (TsiNodeVo)cpuPacket.getObj();//TsiNodeManager.getInstance().get(packet.getNodeId());

        long nodeId = packet.getNodeId();
        if (nodeVo == null) {
            // 노드 정보가 없는 경우
            log.error("Packet process NodeVo is null: {}, {}", nodeId, Thread.currentThread().getName());
            return;
        }

        boolean isLogging = this.traceConfig.isNodeLogging() || nodeVo.isDump();
        String logKey = nodeVo.getKey();
        int loggingIdx = nodeVo.getLogQIdx();   // 로깅인덱스큐로 데이터 전송

        MDC.put("id", logKey);
        try {
            long curr = System.nanoTime();
            if (TimeUnit.MILLISECONDS.convert(curr - packet.getRcv(), TimeUnit.NANOSECONDS) > 3000) {
                log.warn("Packet skip::: {}, {} ms.", nodeId, TimeUnit.MILLISECONDS.convert(curr - packet.getRcv(), TimeUnit.NANOSECONDS));
                return;
            }
            packet.setPop(curr);

            if (packet.getOpCode() == (byte) eOpCode.TSI_CPU_DISCONNECTED.getValue()) {
                try {
                    TsiCpuDisconnected disconnected = (TsiCpuDisconnected) packet;
                    disconnected.parsing(nodeVo);
                    if (nodeVo.isSendCvim() && disconnected.getCvimData() != null) {
                        this.kafkaProducer.sendCvim(disconnected.getNodeId(), disconnected.getCvimData());
                    }

                    if (disconnected.getAddNodes() != null) {
                        // 연등지 인 경우
                        for (int ii = 0; ii < disconnected.getAddNodes().size(); ii++) {
                            this.kafkaProducer.sendCvim(disconnected.getAddNodes().get(ii).getNodeId(), disconnected.getAddNodes().get(ii).getCvimData());
                        }
                    }
                    if (isLogging) {
                        this.loggingService.add(packet, loggingIdx);
                    }
                } catch (Exception e) {
                    if (nodeVo.isConnect()) {
                        log.warn("Node: {}, Disconnect parsing error: {}, connect: {}", nodeId, Thread.currentThread().getName(), nodeVo.isConnect());
                    }
                }
                return;
            }

            // 20250425: packet parsing 의 로그를 노드별 로그파일에 저장되도록 MDC 위치 변경
            // 20250425: parsing 함수에 packet-check 여부를 같이 넘겨줘서 CRC 체크여부 확인
            try {
                int result = cpuPacket.parsing(nodeVo, this.cvimServerConfig.isCheckPacket());
                if (0 != result) {
                    if (-4 == result) {
                        return; // 패킷 버퍼가 null 이거나 너무 짧은 경우
                    }
                    if (!nodeVo.isConnect()) {
                        return; // 연결이 끊어진 경우
                    }

                    byte[] buf = cpuPacket.getBuf();
                    log.error("Node: {}, CPU Packet parsing failed. error: {}.", nodeId, result);
                    log.error("{}", HexString.fromBytes(buf));
                    byte stx1 = 0x00, stx2 = 0x00, version = 0x00;
                    if (buf.length > TsiCpuPacket.INDEX_VERSION) {
                        stx1 = buf[TsiCpuPacket.INDEX_STX1];
                        stx2 = buf[TsiCpuPacket.INDEX_STX2];
                        version = buf[TsiCpuPacket.INDEX_VERSION];
                    }

                    int reqLength = TsiCpuPacket.SIZE_PACKET_DATA + (TsiCpuPacket.SIZE_STATUS_DATA * cpuPacket.getCount());
                    switch (result) {
                        case -1:
                            log.error("Node: {}, STX Error: {}, {}", nodeId, stx1, stx2);
                            break;
                        case -2:
                            log.error("Node: {}, Length Error: {}, Version: {}, status count: {}, {}", nodeId, cpuPacket.getLength(), version,
                                    cpuPacket.getCount(), reqLength);
                            break;
                        case -3:
                            log.error("Node: {}, Check Sum Error: Version: {}, recv: {}, calc: {}", nodeId, version, cpuPacket.getCheckSum(),
                                    cpuPacket.getCalcCheckSum());
                            break;
                        default:
                            log.error("Node: {}, Packet parsing error: {}", nodeId, result);
                            break;
                    }
                    return;
                }
            } catch (Exception e) {
                log.warn("Node: {}, CPU Packet parsing error: {}, connect: {}", nodeId, Thread.currentThread().getName(), nodeVo.isConnect());
            }

            packet.setPar(System.nanoTime());

            // 카프카 전송
            if (nodeVo.isSendNode() && packet.getNodeData() != null) {
                this.kafkaProducer.sendNode(Long.toString(nodeId), packet.getNodeData());
            }
            if (nodeVo.isSendTest()) {
                this.kafkaProducer.sendTest(nodeId, packet.getTestData());
            }
            if (nodeVo.isSendCvim() && packet.getCvimData() != null) {
                this.kafkaProducer.sendCvim(nodeId, packet.getCvimData());
            }

            if (cpuPacket.getAddNodes() != null) {
                // 연등지 인 경우
                for (int ii = 0; ii < cpuPacket.getAddNodes().size(); ii++) {
                    if (nodeVo.isSendNode()) {
                        this.kafkaProducer.sendNode(Long.toString(cpuPacket.getAddNodes().get(ii).getNodeId()), cpuPacket.getAddNodes().get(ii).getNodeData());
                    }
                    if (nodeVo.isSendCvim()) {
                        this.kafkaProducer.sendCvim(cpuPacket.getAddNodes().get(ii).getNodeId(), cpuPacket.getAddNodes().get(ii).getCvimData());
                    }
                }
            }

            packet.setEnd(System.nanoTime());

            packet.setAvg(calcProcessTime(packet.getRcv()));

            // 로그큐로 전송한다.
            if (isLogging) {
                this.loggingService.add(packet, loggingIdx);
            }

            // 연등지 인 경우
            if (cpuPacket.getAddNodes() != null) {
                for (int ii = 0; ii < cpuPacket.getAddNodes().size(); ii++) {
                    cpuPacket.getAddNodes().get(ii).setAdd(packet.getAdd());
                    cpuPacket.getAddNodes().get(ii).setPop(packet.getPop());
                    cpuPacket.getAddNodes().get(ii).setPar(packet.getPar());
                    cpuPacket.getAddNodes().get(ii).setEnd(packet.getEnd());
                    cpuPacket.getAddNodes().get(ii).setAvg(packet.getAvg());
                    if (isLogging) {
                        this.loggingService.add(cpuPacket.getAddNodes().get(ii), loggingIdx);
                    }
                }
            }
        } finally {
            MDC.clear();
        }

    }

    public void report() {
        log.info("PacketWorker({}), Queue Total/Size/Remain: {}/{}/{}, Average: {}, {}",
                this.idx,
                this.qSize, this.DATA_QUEUE.size(), this.DATA_QUEUE.remainingCapacity(),
                TimeUtils.elapsedTimeStr(this.avgTime),
                Thread.currentThread().getName());
    }

}
