{%- import "kafka.jinja.include" as kafka %}
{%- import "util.jinja.include" as util -%}
{{ util.CommonFileHeader() }}

{%- set uses_cloudevents_message = (root | exists("envelope","CloudEvents/1.0")) %}
#nullable enable

using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Confluent.Kafka;
using Microsoft.Extensions.Logging;

{%- if uses_cloudevents_message %}
{{ kafka.CloudEventsUsings() }}
{%- endif %}

namespace {{ project_name | pascal }}
{
    namespace Tools
    {
        public abstract class DispatcherBase
        {
            protected readonly ILogger _logger;             
            public event Func<Error, Task>? ErrorAsync;

            protected DispatcherBase(ILogger logger)
            {
                _logger = logger;
            }

            /// <summary>
            /// Processes a message from the Kafka topic
            /// </summary>
            internal abstract Task<bool> ProcessMessageAsync(ConsumeResult<byte[], byte[]> messageArgs);
            
            internal virtual Task ProcessErrorAsync(Exception error)
            {
                if (ErrorAsync != null)
                {
                    return Task.WhenAll(Array.ConvertAll(ErrorAsync.GetInvocationList(), e => (Task)(e?.DynamicInvoke(error)??Task.CompletedTask)));
                }
                return Task.CompletedTask;
            }
            {%- if uses_cloudevents_message %}
            {{ kafka.CloudEventsMethods() | indent(12) }}
            {%- endif %}
        }
    } 

    /// <summary>
    /// A simple wrapper for the Kafka consumer client that processes messages and handles errors.
    /// </summary>
    public class KafkaProcessor<TKey, TValue>
    {
        private readonly IConsumer<TKey, TValue> _consumer;
        private readonly ILogger<KafkaProcessor<TKey, TValue>> _logger;
        private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
        private Task? _processingTask;
        private string _topicName = string.Empty;

        /// <summary>
        /// Event that is raised when a message is processed.
        /// </summary>
        public event Func<ConsumeResult<TKey, TValue>, Task>? ProcessMessage;

        /// <summary>
        /// Event that is raised when an error occurs.
        /// </summary>
        public event Func<Exception, Task>? ProcessError;

        /// <summary>
        /// Initializes a new instance of the <see cref="KafkaProcessor{TKey, TValue}"/> class.
        /// </summary>
        /// <param name="consumer">The Kafka consumer instance.</param>
        /// <param name="topicName"></param>
        /// <param name="loggerFactory">Optional logger factory for logging.</param>
        public KafkaProcessor(
            IConsumer<TKey, TValue> consumer,
            string topicName,
            ILoggerFactory? loggerFactory = null)
        {
            _consumer = consumer;
            _topicName = topicName;
            _logger = loggerFactory?.CreateLogger<KafkaProcessor<TKey, TValue>>() ?? new LoggerFactory().CreateLogger<KafkaProcessor<TKey, TValue>>();
        }

        /// <summary>
        /// Starts the message processing loop.
        /// </summary>
        /// <returns>A task that represents the asynchronous operation.</returns>
        public Task StartAsync()
        {
            _processingTask = Task.Run(async () => await ProcessMessagesAsync(_cancellationTokenSource.Token));
            return Task.CompletedTask;
        }

        /// <summary>
        /// Stops the message processing loop.
        /// </summary>
        /// <returns>A task that represents the asynchronous operation.</returns>
        public async Task StopAsync()
        {
            _cancellationTokenSource.Cancel();
            if ( _processingTask != null)
            {
                await _processingTask;
            }
            _consumer.Close();
            _cancellationTokenSource = new CancellationTokenSource();
        }

        private async Task ProcessMessagesAsync(CancellationToken cancellationToken)
        {
            try
            {
                _consumer.Subscribe(_topicName);
                while (!cancellationToken.IsCancellationRequested)
                {
                    try
                    {
                        var consumeResult = _consumer.Consume(cancellationToken);
                        if (consumeResult != null)
                        {
                            try
                            {
                                if (ProcessMessage != null)
                                {
                                    await ProcessMessage(consumeResult);
                                }
                                _consumer.Commit(consumeResult);
                            }
                            catch (Exception ex)
                            {
                                _logger?.LogError(ex, "Error processing message");
                            }
                        }
                    }
                    catch (ConsumeException ex)
                    {
                        try
                        {
                            if (ProcessError != null)
                            {
                                await ProcessError(ex);
                            }
                        }
                        catch (Exception handlerEx)
                        {
                            _logger?.LogError(handlerEx, "Error in error handler");
                        }
                    }
                }
            }
            catch (OperationCanceledException)
            {
                // Expected exception when the processing loop is cancelled.
            }
            _consumer.Unsubscribe();
        }
    }
}