{%- import "cloudevents.jinja.include" as cloudEvents %}
{%- import "mqtt.jinja.include" as mqtt %}
{%- import "util.jinja.include" as util -%}
{{ util.CommonFileHeader() }}

{%- set messagegroups = root.messagegroups %}
{%- 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 MQTTnet;
using MQTTnet.Client;
using Microsoft.Extensions.Logging;

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

namespace {{ project_name | pascal }}
{
    namespace Tools
    {
        /// <summary>
        /// Base class for dispatchers that process messages from an MQTT topic.
        /// </summary>
        public abstract class DispatcherBase
        {
            /// <summary>
            /// The logger instance.
            /// </summary>
            protected readonly ILogger _logger;       
            /// <summary>
            /// Event that is raised when an error occurs.
            /// </summary>
            public event Func<Exception, Task>? ErrorAsync;

            /// <summary>
            /// Initializes a new instance of the <see cref="DispatcherBase"/> class.
            /// </summary>
            /// <param name="logger">The logger instance.</param>
            protected DispatcherBase(ILogger logger)
            {
                _logger = logger;
            }

            /// <summary>
            /// Processes a message from the MQTT topic
            /// </summary>
            internal abstract Task<bool> ProcessMessageAsync(MqttApplicationMessage messageArgs);
            
            /// <summary>
            /// Processes an error that occurred while processing a message.
            /// </summary>
            /// <param name="error">The error that occurred.</param>
            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 %}
            {{ mqtt.CloudEventsMethods() | indent(12) }}
            {%- endif %}
        }
    } 

    /// <summary>
    /// A simple wrapper for the MQTT client that processes messages and handles errors.
    /// </summary>
    public class MqttSubscriber
    {
        private IMqttClient _client;
        private string[] _topics;
        private ILogger<MqttSubscriber>? _logger;
        private CancellationTokenSource? _cancellationTokenSource = new CancellationTokenSource();

        /// <summary>
        /// Event that is raised when a message is processed.
        /// </summary>
        public event Func<MqttApplicationMessage, 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="MqttSubscriber"/> class.
        /// </summary>
        /// <param name="client">The MQTT client instance.</param>
        /// <param name="options">The MQTT client options.</param>
        /// <param name="topics">The topics to subscribe to.</param>
        /// <param name="loggerFactory">Optional logger factory for logging.</param>
        public MqttSubscriber(
            IMqttClient client,
            string[] topics,
            ILoggerFactory? loggerFactory = null)
        {
            if (client == null)
            {
                throw new ArgumentNullException(nameof(client));
            }
            if (topics == null)
            {
                throw new ArgumentNullException(nameof(topics));
            }
            _client = client;
            _topics = topics;
            _logger = loggerFactory?.CreateLogger<MqttSubscriber>() ?? new LoggerFactory().CreateLogger<MqttSubscriber>();
        }

        /// <summary>
        /// Starts the message processing loop.
        /// </summary>
        /// <returns>A task that represents the asynchronous operation.</returns>
        public async Task SubscribeAsync()
        {
            _client.ApplicationMessageReceivedAsync += async (e) => await OnMessageReceived(e.ApplicationMessage);
            var mqttClientSubscribeOptionsBuilder = new MqttClientSubscribeOptionsBuilder();
            foreach (var topic in _topics)
            {
                mqttClientSubscribeOptionsBuilder = mqttClientSubscribeOptionsBuilder.WithTopicFilter(new MqttTopicFilterBuilder().WithTopic(topic).Build());
            }
            await _client.SubscribeAsync(mqttClientSubscribeOptionsBuilder.Build());
        }

        /// <summary>
        /// Stops the message processing loop.
        /// </summary>
        /// <returns>A task that represents the asynchronous operation.</returns>
        public async Task UnsubscribeAsync()
        {
            _cancellationTokenSource.Cancel();
            await _client.DisconnectAsync();
            _cancellationTokenSource = new CancellationTokenSource();
        }

        private async Task OnMessageReceived(MqttApplicationMessage messageArgs)
        {
            try
            {
                if (ProcessMessage != null)
                {
                    await ProcessMessage(messageArgs);
                }
            }
            catch (Exception ex)
            {
                _logger?.LogError(ex, "Error processing message");
                if (ProcessError != null)
                {
                    await ProcessError(ex);
                }
            }
        }
    }
}
