package com.lingniu.ingest.sink.kafka;

import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.ByteArraySerializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

import java.time.Duration;
import java.util.Properties;

/**
 * Kafka Sink 自动装配。
 *
 * <p>{@code lingniu.ingest.sink.kafka.enabled=false} 时本模块完全不装配；Producer 和 Consumer
 * 是两个独立开关，消费端还需要开启 {@code lingniu.ingest.sink.kafka.consumer.enabled=true}
 * 并配置 bindings。
 */
@AutoConfiguration
@EnableConfigurationProperties(KafkaSinkProperties.class)
@ConditionalOnProperty(prefix = "lingniu.ingest.sink.kafka", name = "enabled", havingValue = "true", matchIfMissing = true)
public class KafkaSinkAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public EnvelopeMapper envelopeMapper(KafkaSinkProperties props) {
        return new EnvelopeMapper(props.getNodeId());
    }

    @Bean
    @ConditionalOnMissingBean
    public TopicRouter topicRouter(KafkaSinkProperties props) {
        return new TopicRouter(props.getTopics());
    }

    @Bean
    @ConditionalOnMissingBean
    public CircuitBreaker kafkaSinkCircuitBreaker() {
        return CircuitBreaker.of("kafka-sink", CircuitBreakerConfig.custom()
                .slidingWindowSize(100)
                .failureRateThreshold(50)
                .waitDurationInOpenState(Duration.ofSeconds(10))
                .permittedNumberOfCallsInHalfOpenState(10)
                .build());
    }

    @Bean(destroyMethod = "close")
    @ConditionalOnMissingBean
    public KafkaProducer<String, byte[]> kafkaProducer(KafkaSinkProperties props) {
        Properties p = new Properties();
        p.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, props.getBootstrapServers());
        p.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        p.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, ByteArraySerializer.class.getName());
        p.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, props.getCompressionType());
        p.put(ProducerConfig.LINGER_MS_CONFIG, props.getLingerMs());
        p.put(ProducerConfig.BATCH_SIZE_CONFIG, props.getBatchSize());
        p.put(ProducerConfig.ACKS_CONFIG, props.getAcks());
        p.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, props.isEnableIdempotence());
        return new KafkaProducer<>(p);
    }

    @Bean(destroyMethod = "close")
    @ConditionalOnMissingBean
    public KafkaEventSink kafkaEventSink(KafkaProducer<String, byte[]> producer,
                                         EnvelopeMapper mapper,
                                         TopicRouter router,
                                         KafkaSinkProperties props,
                                         CircuitBreaker breaker) {
        // KafkaEventSink 是 EventBus 的生产端出口；它不会启动任何 Kafka 消费线程。
        return new KafkaEventSink(producer, mapper, router, props.getTopics().getDlq(), breaker);
    }

    @Bean
    @ConditionalOnMissingBean
    public KafkaEnvelopeDeadLetterSink kafkaEnvelopeDeadLetterSink(KafkaProducer<String, byte[]> producer,
                                                                   KafkaSinkProperties props) {
        return new KafkaEnvelopeDeadLetterSink(producer, props.getTopics().getDlq());
    }

}
