package com.tsi.sig.server.websocket.kafka;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.tsi.sig.server.app.AppUtils;
import com.tsi.sig.server.config.KafkaConsumerConfig;
import com.tsi.sig.server.repository.ApplicationRepository;
import com.tsi.sig.server.vo.CvibNodeStatusVo;
import com.tsi.sig.server.vo.CvibNodeTurnStatusVo;
import com.tsi.sig.server.websocket.AllTopicCvimHandler;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRebalanceListener;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.errors.WakeupException;
import org.springframework.web.socket.WebSocketSession;

import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

@Slf4j
@Getter
@Setter
public class AllTopicConsumerThread implements Runnable {

    private AtomicBoolean stopFlag = new AtomicBoolean(true);

    private final Set<WebSocketSession> sessionSet = new HashSet<>();
    private final AllTopicCvimHandler websocketHandler;
    private final KafkaConsumerConfig kafkaConsumerConfig;
    private final ApplicationRepository repo;

    private List<Map<String, Object>> signalData = new ArrayList<Map<String, Object>>();
    private KafkaConsumer<String, ByteBuffer> consumer;
    private String topicName;
    private ObjectMapper mapper;

    public AllTopicConsumerThread(AllTopicCvimHandler websocketHandler, KafkaConsumerConfig kafkaConsumerConfig) {
        this.repo = (ApplicationRepository) AppUtils.getBean(ApplicationRepository.class);
        this.websocketHandler = websocketHandler;
        this.kafkaConsumerConfig = kafkaConsumerConfig;
        this.consumer = new KafkaConsumer<>(this.kafkaConsumerConfig.getConsumerAllProperties());
        this.topicName = this.kafkaConsumerConfig.getAllTopic();
        this.mapper = new ObjectMapper();
    }

    private void SetDataMapALL(String key, ByteBuffer value, int valueSize) {
        CvibNodeStatusVo node = this.repo.getNodeStatusMap().get(key);
        if (node == null) {
            node = new CvibNodeStatusVo(key);
            this.repo.getNodeStatusMap().put(key, node);
        }

        if (value.get(22) == 0) { //통신OFF일때 [1이 정상 0에러]
            //header.put("errorMsg", "통신 OFF");
            node = new CvibNodeStatusVo(key);
            node.commStatus = false;
            return;
        }
        else if (value.get(22) == 1) {
            node.commStatus = true;
        }

        node.counter    = value.get(39) & 0xff;
        node.sttsCount  = value.get(40) & 0x7F;           //신호정보개수
        node.divFlag    = (value.get(40) >> 7) & 0x01;      //분활flag

        node.oprTrans   = (value.get(37) >> 4) & 0x01;
        node.oprInd     = (value.get(37) >> 3) & 0x01;
        node.oprTurnoff = (value.get(37) >> 2) & 0x01;
        node.oprBlink   = (value.get(37) >> 1) & 0x01;
        node.oprManual  = (value.get(37)) & 0x01;

        node.errScu     = (value.get(38) >> 2) & 0x01;
        node.errCenter  = (value.get(38) >> 1) & 0x01;
        node.errCont    = (value.get(38)) & 0x01;
        node.dataCount  = node.sttsCount;

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = new Date(((long) value.getInt(41) & 0xffffffffL) * 1000L);
        sdf.setTimeZone(TimeZone.getTimeZone("GMT+9"));

        node.localDate = sdf.format(date);
        node.connDate = sdf.format(new Date());

        int idx = 0;
        List<CvibNodeTurnStatusVo> turns = new ArrayList<>();
        for (int ii = 0; ii < node.sttsCount; ii++) {
            //Map<String, Object> temp = new HashMap<>();
            CvibNodeTurnStatusVo stts = new CvibNodeTurnStatusVo();
            idx = ii * 5;

            stts.light       = (value.get(45 + idx) >> 4) & 0x0F;
            stts.dirAdd      = (value.get(45 + idx)) & 0x0F;
            stts.timeFlag    = (value.get(46 + idx) >> 7) & 0x01;
            stts.walker      = (value.get(46 + idx) >> 6) & 0x01;
            stts.unprotected = (value.get(46 + idx) >> 3) & 0x01;
            stts.stts        = (value.get(46 + idx)) & 0x07;
            stts.dispTm      = value.get(47 + idx) & 0xff;
            stts.remainTm    = value.get(48 + idx) & 0xff;
            stts.dirCode     = value.get(49 + idx);

            turns.add(stts);
        }
        node.turns = turns;
        if (turns.size() == 0) {
            node.commStatus = false;
        }

    }

    public List<String> formatPartitions(Collection<TopicPartition> partitions) {
        return partitions.stream().map(topicPartition ->
                        String.format("\ntopic: %s, partition: %s", topicPartition.topic(), topicPartition.partition()))
                .collect(Collectors.toList());
    }

    @Override
    public void run() {

        log.info("AllTopicConsumerThread, Start consumer: {}", this.topicName);

        this.stopFlag.set(false);

        try {
            this.consumer.subscribe(Collections.singletonList(this.topicName),
                    new ConsumerRebalanceListener() {
                        @Override
                        public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
                            log.info("AllTopicConsumerThread, onPartitionsRevoked - consumerName: {}, partitions: {}", topicName, formatPartitions(partitions));
                        }

                        @Override
                        public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
                            log.info("AllTopicConsumerThread, onPartitionsAssigned - consumerName: {}, partitions: {}", topicName, formatPartitions(partitions));
                            consumer.seekToEnd(partitions);
                        }
                    });
            while (!this.stopFlag.get() && (!Thread.currentThread().isInterrupted())) {

                ConsumerRecords<String, ByteBuffer> records = this.consumer.poll(Duration.ofMillis(100));

                for (ConsumerRecord<String, ByteBuffer> record : records) {

                    SetDataMapALL(record.key(), record.value(), record.serializedValueSize());
//                    try {
//                        String jsonInString = this.mapper.writeValueAsString(this.signalData);
//                        synchronized (this.sessionSet) {
//                            for (WebSocketSession session : this.sessionSet) {
//                                try {
//                                    this.websocketHandler.sendMessage(session, new TextMessage(jsonInString));
//                                    //log.info("AllTopicConsumerThread, Send to: {}, {}, {} bytes.", session.getRemoteAddress().getAddress(), record.key(), jsonInString.length());
//                                }
//                                catch(IOException e) {
//                                    log.error("AllTopicConsumerThread, Send Failed: {}, {}, {} bytes.", session, record.key(), jsonInString.length());
//                                }
//                            }
//                        }
//                    }
//                    catch(JsonProcessingException e) {
//                        log.error("AllTopicConsumerThread, ConsumerThread Json parsing Exception: {}, {}", this.topicName, e.getMessage());
//                        log.error("AllTopicConsumerThread, ConsumerThread Json parsing Exception: {}", this.topicName);
//                    }
                    this.signalData.clear();
                }
            }
            log.info("AllTopicConsumerThread, ConsumerThread: {}, stopped.", this.topicName);
        }
        catch(WakeupException e) {
            log.error("AllTopicConsumerThread, Consumer WakeupException: {}, {}", this.topicName, e);
            stop();
        }
        finally {
            //this.consumer.commitSync();
            stop();
            try {
                this.consumer.close();
            } catch(IllegalArgumentException e) {
                log.error("Could Not Support Method : this.consumer.close()");
            }
        }
    }

    public void addSession(WebSocketSession session) {
        synchronized (this.sessionSet) {
            this.sessionSet.add(session);
        }
    }

    public void removeSession(WebSocketSession session) {
        log.info("AllTopicConsumerThread, remove sessions: {}", this.sessionSet.toString());
        synchronized (this.sessionSet) {
            this.sessionSet.remove(session);
        }
    }

    public void stop() {
        this.stopFlag.set(true);
    }

    public void shutdown() {
        log.info("AllTopicConsumerThread, shutdown wakeup: {}", this.getClass().getSimpleName());
        this.consumer.wakeup();
    }
}
