package de.accso.flexinale.common.infrastructure.eventbus;

import org.apache.kafka.clients.consumer.Consumer;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.listener.CommonErrorHandler;
import org.springframework.kafka.listener.DeadLetterPublishingRecoverer;
import org.springframework.kafka.listener.MessageListenerContainer;

// Error handling for Kafka consumers using retry pattern and finally writing error messages to DLT

/*
A typical Spring Kafka error handler would look like below, but this only works for consumers
which are annotated with @KafkaListener

...
	private int numberOfMaxRetriesForRetryableErrors = 3;

	@Bean
	DefaultErrorHandler kafkaConsumerErrorHandler() {
		DefaultErrorHandler errorHandler = new DefaultErrorHandler((record, ex) -> {
			LOGGER.error("error while processing Kafka record " + record + ex);

			// finally send to dead letter topics
			new DeadLetterPublishingRecoverer(internal_KafkaErrorDLTTemplate).accept(record, ex);
		},
		// Default error handling is to retry
		new ExponentialBackOffWithMaxRetries(numberOfMaxRetriesForRetryableErrors));

		// ... but do not retry on FlexinaleKafkaNotRetryableException and its subclasses
		errorHandler.addNotRetryableExceptions(FlexinaleKafkaNotRetryableException.class);

		return errorHandler;
	}
...

 */

public final class KafkaConsumerErrorHandler implements CommonErrorHandler {
    private static final Logger LOGGER = LoggerFactory.getLogger(KafkaConsumerErrorHandler.class);

    // Kafka template for sending error message to DLT
    static final KafkaTemplate<String, String> internal_KafkaErrorDLTTemplate = new KafkaTemplate<>(
            new DefaultKafkaProducerFactory<>(
                    KafkaConfiguration.producerConfig()
            ));

    @Override
    public boolean handleOne(Exception thrownException, ConsumerRecord<?, ?> record, Consumer<?, ?> consumer,
                             MessageListenerContainer container) {
        try {
            LOGGER.error("error while processing Kafka record %s, now sending to DLT, error: %s".formatted(record, thrownException));
            new DeadLetterPublishingRecoverer(internal_KafkaErrorDLTTemplate).accept(record, thrownException);
            return true;
        }
        catch (Exception ex) {
            return false;
        }
    }
}

abstract class FlexinaleKafkaNotRetryableException extends RuntimeException {
    public FlexinaleKafkaNotRetryableException(final String message, final Throwable cause) {
        super(message, cause);
    }
}

@SuppressWarnings("unused")
class FlexinaleKafkaMessageDeserializationException extends FlexinaleKafkaNotRetryableException {
    public FlexinaleKafkaMessageDeserializationException(final String message, final Throwable cause) {
        super(message, cause);
    }
}
