package com.lingniu.ingest.sink.kafka;

import com.lingniu.ingest.api.consumer.EnvelopeConsumerProcessor;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.serialization.ByteArrayDeserializer;
import org.apache.kafka.common.serialization.StringDeserializer;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.function.Function;

public final class KafkaEnvelopeConsumerFactory {

    private final Function<Properties, org.apache.kafka.clients.consumer.Consumer<String, byte[]>> consumerFactory;

    public KafkaEnvelopeConsumerFactory() {
        this(KafkaConsumer::new);
    }

    KafkaEnvelopeConsumerFactory(
            Function<Properties, org.apache.kafka.clients.consumer.Consumer<String, byte[]>> consumerFactory) {
        this.consumerFactory = consumerFactory;
    }

    public List<KafkaEnvelopeConsumerWorker> createWorkers(Map<String, EnvelopeConsumerProcessor> processors,
                                                           KafkaSinkProperties props) {
        Map<String, KafkaSinkProperties.Binding> bindings = effectiveBindings(props);
        List<KafkaEnvelopeConsumerWorker> workers = new ArrayList<>();
        for (Map.Entry<String, KafkaSinkProperties.Binding> entry : bindings.entrySet()) {
            String processorBeanName = entry.getKey();
            // binding 的 key 必须和 Spring Bean 名一致；这样配置只声明 topic/group，
            // 实际处理逻辑仍由各业务模块自己的 EnvelopeConsumerProcessor 承接。
            EnvelopeConsumerProcessor processor = processors.get(processorBeanName);
            KafkaSinkProperties.Binding binding = entry.getValue();
            if (processor == null || binding == null || !binding.isEnabled()) {
                continue;
            }
            List<String> topics = cleanTopics(binding.getTopics());
            if (topics.isEmpty()) {
                continue;
            }
            int concurrency = Math.max(1, props.getConsumer().getConcurrency());
            for (int workerIndex = 0; workerIndex < concurrency; workerIndex++) {
                KafkaEnvelopeConsumerWorker worker = new KafkaEnvelopeConsumerWorker(
                        consumerFactory.apply(consumerProperties(props, binding, processorBeanName,
                                workerIndex, concurrency)),
                        topicProcessors(topics, processor));
                worker.subscribe(topics);
                workers.add(worker);
            }
        }
        return workers;
    }

    private Map<String, KafkaSinkProperties.Binding> effectiveBindings(KafkaSinkProperties props) {
        Map<String, KafkaSinkProperties.Binding> configured = props.getConsumer().getBindings();
        if (configured != null && !configured.isEmpty()) {
            return configured;
        }
        // 默认绑定仅给未显式配置 bindings 的轻量运行时兜底。
        // 生产 history app 会显式绑定各协议 event/raw topic，并写入 TDengine raw_frames/locations。
        KafkaSinkProperties.Topics topics = props.getTopics();
        Map<String, KafkaSinkProperties.Binding> defaults = new LinkedHashMap<>();
        defaults.put("eventHistoryEnvelopeConsumerProcessor", binding(
                "vehicle-event-history",
                topics.getRealtime(), topics.getLocation(), topics.getAlarm(), topics.getSession(), topics.getMediaMeta()));
        defaults.put("vehicleStateEnvelopeConsumerProcessor", binding(
                "vehicle-state",
                topics.getRealtime(), topics.getLocation(), topics.getAlarm()));
        defaults.put("vehicleStatEnvelopeConsumerProcessor", binding(
                "vehicle-stat",
                topics.getRealtime(), topics.getLocation()));
        return defaults;
    }

    private KafkaSinkProperties.Binding binding(String groupId, String... topics) {
        KafkaSinkProperties.Binding binding = new KafkaSinkProperties.Binding();
        binding.setGroupId(groupId);
        binding.setTopics(List.of(topics));
        return binding;
    }

    private Map<String, EnvelopeConsumerProcessor> topicProcessors(List<String> topics, EnvelopeConsumerProcessor processor) {
        Map<String, EnvelopeConsumerProcessor> byTopic = new LinkedHashMap<>();
        for (String topic : topics) {
            byTopic.put(topic, processor);
        }
        return byTopic;
    }

    private List<String> cleanTopics(List<String> topics) {
        if (topics == null || topics.isEmpty()) {
            return List.of();
        }
        LinkedHashSet<String> clean = new LinkedHashSet<>();
        for (String topic : topics) {
            if (topic != null && !topic.isBlank()) {
                clean.add(topic);
            }
        }
        return List.copyOf(clean);
    }

    private Properties consumerProperties(KafkaSinkProperties props,
                                          KafkaSinkProperties.Binding binding,
                                          String processorBeanName,
                                          int workerIndex,
                                          int concurrency) {
        KafkaSinkProperties.Consumer consumer = props.getConsumer();
        Properties p = new Properties();
        p.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, props.getBootstrapServers());
        p.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        p.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ByteArrayDeserializer.class.getName());
        p.put(ConsumerConfig.GROUP_ID_CONFIG, groupId(binding, processorBeanName));
        p.put(ConsumerConfig.CLIENT_ID_CONFIG, clientId(consumer.getClientIdPrefix(), processorBeanName,
                workerIndex, concurrency));
        // 手动提交 offset：只有本批次至少有一条记录被处理器接收后才 commit，
        // 避免轮询到空批次或未绑定 topic 时推进消费位点。
        p.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
        p.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, consumer.getAutoOffsetReset());
        p.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, consumer.getMaxPollRecords());
        p.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, consumer.getMaxPollIntervalMillis());
        return p;
    }

    private String clientId(String prefix, String processorBeanName, int workerIndex, int concurrency) {
        String base = prefix + "-" + processorBeanName;
        return concurrency <= 1 ? base : base + "-" + workerIndex;
    }

    private String groupId(KafkaSinkProperties.Binding binding, String processorBeanName) {
        if (binding.getGroupId() != null && !binding.getGroupId().isBlank()) {
            return binding.getGroupId();
        }
        return processorBeanName.replace("EnvelopeConsumerProcessor", "");
    }
}
