package com.yeejoin.equip.kafka;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.yeejoin.equip.config.KafkaConsumerConfig;
import com.yeejoin.equip.entity.Book;
import com.yeejoin.equip.entity.EquipmentIndexVO;
import com.yeejoin.equip.entity.EsEntity;
import com.yeejoin.equip.entity.IndicatorData;
import com.yeejoin.equip.mapper.tdengine.IndicatorDataMapper;
import com.yeejoin.equip.utils.ElasticSearchUtil;
import com.yeejoin.equip.utils.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

/**
 * @author LiuLin
 * @date 2023年08月01日 17:27
 */
@Slf4j
@Component
public class KafkaConsumerWorker implements CommandLineRunner {
    final private static AtomicLong sendThreadPoolCounter = new AtomicLong(0);
    final public static ExecutorService pooledExecutor =
        Executors.newFixedThreadPool(200 + Runtime.getRuntime().availableProcessors(),
                createThreadFactory());

    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = 6 * CPU_COUNT;
    private static final int MAX_POOL_SIZE = 6 * CPU_COUNT + 2;
    /* private static final ThreadPoolExecutor exec = new ThreadPoolExecutor(
            CORE_POOL_SIZE,
            MAX_POOL_SIZE,
            60L,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(1000)
    );*/

    private static final String ES_INDEX_NAME_JX = "jxiop_equipments";
    private static final String TRANSFORMATION = "transformation";
    //装备更新最新消息存入influxdb前缀
    private static final String TRUE = "true";
    private static final String FALSE = "false";

    @Autowired
    protected KafkaProducerService kafkaProducerService;

    @Autowired
    private KafkaConsumerConfig consumerConfig;

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private IndicatorDataMapper indicatorDataMapper;

    @Value("${kafka.alarm.topic}")
    private String alarmTopic;

    @Value("${kafka.topic}")
    private String topic;

    @Autowired
    private ElasticSearchUtil elasticSearchUtil;

    private static ThreadFactory createThreadFactory() {
        return runnable -> {
            Thread thread = new Thread(runnable);
            thread.setName(String.format("kafka-consumer-iot-pool-%d", KafkaConsumerWorker.sendThreadPoolCounter.getAndIncrement()));
            return thread;
        };
    }

    @Override
    public void run(String... args) {
        Thread thread = new Thread(new KafkaConsumerThread(consumerConfig.consumerConfigs(), topic));
        thread.start();
    }

    private Optional<IndicatorData> processSignal(ConsumerRecord<String, String> record) {
        JSONObject jsonObject = JSONObject.parseObject(record.value());
        String address = jsonObject.getString("address");
        String gatewayId = jsonObject.getString("gatewayId");
        String value = jsonObject.getString("value");
        String key = address + "_" + gatewayId;
        log.info("===========收到Kafka消息，key:{},value:{}", key, value);
        IndicatorData indicatorData = JSON.parseObject(record.value(), IndicatorData.class);
        if (redisUtils.hasKey(key)) {
            EquipmentIndexVO equipmentSpeIndex = JSONObject.parseObject(redisUtils.get(key), EquipmentIndexVO.class);
            String valueLabel = valueTranslate(value, equipmentSpeIndex.getValueEnum());
            indicatorData.setIsAlarm(String.valueOf(equipmentSpeIndex.getIsAlarm()));
            indicatorData.setEquipmentIndexName(equipmentSpeIndex.getEquipmentIndexName());
            indicatorData.setEquipmentSpecificName(equipmentSpeIndex.getEquipmentSpecificName());
            indicatorData.setUnit(equipmentSpeIndex.getUnitName());
            indicatorData.setEquipmentsIdx(key);
            indicatorData.setValueLabel(valueLabel.isEmpty() ? value : valueLabel);
            indicatorData.setValueF(!Arrays.asList(TRUE, FALSE).contains(value) ? Float.parseFloat(value) : 0);
            //发送告警信息
            if (0 != equipmentSpeIndex.getIsAlarm()) {
                kafkaProducerService.sendMessageAsync(alarmTopic, JSON.toJSONString(indicatorData));
                log.info("===========发送告警信息，key:{}", indicatorData.getEquipmentsIdx());
            }
            return Optional.of(indicatorData);
        }
        return Optional.empty();
    }

    private void processRecord(ConsumerRecords<String, String> records) {
        Map<String, List<IndicatorData>> data = StreamSupport.stream(records.spliterator(), true)
                .map(this::processSignal)
                .filter(Optional::isPresent)
                .map(Optional::get)
                .collect(Collectors.groupingBy(IndicatorData::getGatewayId));

        data.forEach((gatewayId, list) -> {
            //1.update es
            List<EsEntity<Book>> batchList = new ArrayList<>(list.size());
            list.forEach(item -> batchList.add(new EsEntity<>(item.getEquipmentsIdx(), new Book(item.getValue(), item.getValueF(), item.getValueLabel(),
                    item.getUnit(), new Date()))));
            elasticSearchUtil.updateBatch(ES_INDEX_NAME_JX, batchList);
            //2.save
            List<IndicatorData> tdDataList = list.stream().filter(t -> Objects.equals(t.getSignalType(), TRANSFORMATION)).collect(Collectors.toList());
            indicatorDataMapper.insertBatch(tdDataList, gatewayId);
            tdDataList.forEach(s -> log.info("===========TDEngine入库成功，id：【{}】，value：【{}】修改成功", s.getEquipmentsIdx(), s.getValueF()));
        });
    }

    private String valueTranslate(String value, String enumStr) {
        if (ObjectUtils.isEmpty(enumStr)) {
            return "";
        }
        try {
            JSONArray jsonArray = JSONArray.parseArray(enumStr);
            for (int i = 0; i < jsonArray.size(); i++) {
                JSONObject jsonObject = jsonArray.getJSONObject(i);
                if (jsonObject.get("key").equals(value)) {
                    return jsonObject.getString("label");
                }
            }
        } catch (Exception e) {
            log.error("告警枚举转换异常" + e.getMessage(), e);
        }
        return "";
    }

    public class KafkaConsumerThread implements Runnable {
        private final KafkaConsumer<String, String> kafkaConsumer;

        public KafkaConsumerThread(Properties props, String topic) {
            this.kafkaConsumer = new KafkaConsumer<>(props);
            this.kafkaConsumer.subscribe(Collections.singletonList(topic));
        }

        @Override
        public void run() {
            while (true) {
                ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofMillis(100));
                pooledExecutor.submit(() -> {
                    processRecord(records);
                });
                kafkaConsumer.commitSync();
            }
        }
    }
}
