{%- import "cloudevents.jinja.include" as cloudEvents -%}
{%- import "amqp.jinja.include" as amqp -%}
{%- import "util.jinja.include" as util -%}
{{ util.CommonFileHeader() }}
{%- set messagegroups = root.messagegroups %}
{%- set uses_cloudevents_message = cloudEvents.usesCloudEvents(root) %}
{%- set uses_amqp_message = amqp.uses_amqp_protocol(root) %}
{%- set uses_amqp_endpoint = amqp.uses_amqp_endpoint(root) %}
{%- set function_name = project_name | pascal | strip_dots -%}
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using System.Text.RegularExpressions;

{%- if uses_cloudevents_message %}
{{ cloudEvents.CloudEventsUsings() }}
{%- endif %}
{{ amqp.AmqpNetLiteHeaders() }}
using Microsoft.Extensions.Logging;

namespace {{ project_name | pascal }}
{
    {% for messagegroupid, messagegroup in messagegroups.items() -%}
    {%- set groupname = messagegroupid | pascal -%}
    {%- set class_name = (groupname | strip_namespace) + "EventConsumer" %}
    /// <summary>
    /// Consumer for {{ groupname }} message group events.
    /// Provides event handlers for incoming AMQP messages.
    /// </summary>
    /// <remarks>
    /// Use <see cref="CreateConsumer"/> to instantiate or <see cref="{{ class_name }}Builder"/> for fluent configuration.
    /// Implements IAsyncDisposable for proper resource cleanup.
    /// </remarks>
    public class {{ class_name }} : {{project_name | pascal }}.Tools.DispatcherBase, IAsyncDisposable
    {
        private readonly AmqpConsumer _endpoint;

        {%- for messageid, message in messagegroup.messages.items() -%}
        {%- set messagename = messageid | strip_dots | pascal -%}
        {%- set message_body_type = util.body_type(data_project_name, root, message) %}
        {%- if cloudEvents.isCloudEvent(message) %}
        /// <summary>
        /// Event handler for {{ messagename }} messages.
        /// </summary>
        public event Func<CloudEvent, {{ message_body_type }}?, MessageContext, Task>? {{ messagename }}Async;
        {%- else %}
        /// <summary>
        /// Event handler for {{ messagename }} messages.
        /// </summary>
        public event Func<Message, {{ message_body_type }}?, MessageContext, Task>? {{ messagename }}Async;
        {%- endif %}
        {%- endfor %}
        {%- if uses_amqp_message%}
        /// <summary>
        /// Event handler for unhandled AMQP messages.
        /// </summary>
        public event Func<Message, MessageContext, Task>? UnhandledMessageAsync;   
        {%- endif %}
        {%- if uses_cloudevents_message%}
        /// <summary>
        /// Event handler for unhandled CloudEvents.
        /// </summary>
        public event Func<CloudEvent, MessageContext, Task>? UnhandledEventAsync;
        {%- endif %}
        /// <summary>
        /// Event handler for message processing errors.
        /// </summary>
        public event Func<CloudEvent, Exception, Task>? MessageProcessingErrorAsync;
        /// <summary>
        /// Event handler for deserialization errors.
        /// </summary>
        public event Func<CloudEvent, Exception, Task>? DeserializationErrorAsync;
        
        private {{ class_name }}(AmqpConsumer endpoint, ILoggerFactory? logger)
            : base(logger)
        {
            _endpoint = endpoint;
            {% if uses_amqp_message %}
            _endpoint.DispatchMessageAsync += async (message, context, log) => await DispatchMessageAsync(message, context);
            {%- endif %}
            {%- if uses_cloudevents_message %}
            _endpoint.DispatchCloudEventAsync += async (cloudEvent, context, log) => await DispatchCloudEventAsync(cloudEvent, context);
            {%- endif %}
        }

        /// <summary>
        /// Creates a consumer with custom endpoint configuration.
        /// </summary>
        /// <param name="credential">Credentials for AMQP authentication.</param>
        /// <param name="endpoint">AMQP broker URI.</param>
        /// <param name="loggerFactory">Optional logger factory for diagnostics.</param>
        /// <param name="options">AMQP protocol options (e.g., node, filter).</param>
        /// <returns>Configured consumer instance.</returns>
        /// <example>
        /// <code>
        /// var consumer = {{ class_name }}.CreateConsumer(
        ///     new PlainEndpointCredential("user", "pass"),
        ///     new Uri("amqp://localhost:5672"),
        ///     loggerFactory,
        ///     new Dictionary&lt;string, string&gt; { ["node"] = "/queue" });
        /// consumer.{{ messagegroup.messages.keys() | first | strip_dots | pascal }}Async += async (ce, data, context) => {
        ///     // Handle message
        ///     await ProcessAsync(data);
        ///     context.Accept();
        /// };
        /// await consumer.StartAsync();
        /// </code>
        /// </example>
        public static {{ class_name }} CreateConsumer(EndpointCredential credential, Uri endpoint, ILoggerFactory? loggerFactory = null, Dictionary<string, string>? options = null)
        {
            var amqpConsumer = new AmqpConsumer(credential, endpoint, options, loggerFactory);
            return new {{ class_name }}(amqpConsumer, loggerFactory);
        }

        /// <summary>
        /// Creates a builder for fluent consumer configuration.
        /// </summary>
        /// <returns>Builder instance for configuring the consumer.</returns>
        /// <example>
        /// <code>
        /// var consumer = {{ class_name }}.CreateBuilder()
        ///     .WithEndpoint("amqp://localhost:5672")
        ///     .WithCredential(new PlainEndpointCredential("user", "pass"))
        ///     .WithNode("/queue")
        ///     .WithLogger(loggerFactory)
        ///     .Build();
        /// </code>
        /// </example>
        public static {{ class_name }}Builder CreateBuilder()
        {
            return new {{ class_name }}Builder();
        }

        /// <summary>
        /// Starts consuming messages from the AMQP endpoint.
        /// </summary>
        public Task StartAsync() => _endpoint.StartAsync();
        
        /// <summary>
        /// Stops consuming messages from the AMQP endpoint.
        /// </summary>
        public Task StopAsync() => _endpoint.StopAsync();

        {%- if uses_cloudevents_message %}
        private async Task<bool> DispatchCloudEventAsync(CloudEvent cloudEvent, MessageContext context)
        {
            var cloudEventType = cloudEvent.Type;
            _logger.LogInformation($"Dispatching CloudEvent of type {cloudEventType}");
            switch (cloudEventType)
            {
                {% for messageid, message in messagegroup.messages.items() -%}
                {%- set messagename = messageid | strip_dots | pascal -%}
                {%- set message_body_type = util.body_type(data_project_name, root, message) %}
                {%- if "type" in message.envelopemetadata and "value" in message.envelopemetadata["type"] -%}
                case "{{ message.envelopemetadata["type"]["value"] }}":
                {%- else -%}
                case "{{ messageid }}":
                {%- endif %}
                    if ({{ messagename }}Async != null)
                    {
                        var tasks = new List<Task>();
                        foreach (var handler in {{ messagename }}Async.GetInvocationList())
                        {
                            if ( handler == null ) continue;
                            {%- if message_body_type != "byte[]" -%}
                            try
                            {
                                var t = (Task?)(handler.DynamicInvoke(
                                    cloudEvent,
                                    {{ message_body_type }}.FromData(cloudEvent.Data, cloudEvent.DataContentType),
                                    context));
                                if (t != null)
                                {
                                    tasks.Add(t);
                                }
                            }
                            catch (Exception ex)
                            {
                                _logger.LogError(ex, "Error deserializing {{ messagename }} message");
                                if (DeserializationErrorAsync != null)
                                {
                                    await DeserializationErrorAsync(cloudEvent, ex);
                                }
                            }
                            {%- else -%}
                            byte[]? dataBytes = null;
                            if (cloudEvent.Data != null)
                            {
                                if (cloudEvent.Data is byte[] bytes)
                                {
                                    dataBytes = bytes;
                                }
                                else if (cloudEvent.Data is BinaryData binaryData)
                                {
                                    dataBytes = binaryData.ToArray();
                                }
                                else if (cloudEvent.Data is System.Text.Json.JsonElement jsonElement)
                                {
                                    dataBytes = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(jsonElement);
                                }
                            }
                            var t = (Task?)(handler.DynamicInvoke(cloudEvent, dataBytes, context));
                            if (t != null)
                            {
                                tasks.Add(t);
                            }
                            {%- endif -%}
                        }
                        try
                        {
                            await Task.WhenAll(tasks);
                        }
                        catch (Exception ex)
                        {
                            _logger.LogError(ex, "Error processing {{ messagename }} message");
                            if (MessageProcessingErrorAsync != null)
                            {
                                await MessageProcessingErrorAsync(cloudEvent, ex);
                            }
                        }
                    }
                    return true;
                {% endfor %}
                default:
                    if ( UnhandledEventAsync != null )
                    {
                        await UnhandledEventAsync(cloudEvent, context);
                    }
                    return false;
            }
        }
        {%- endif %}

        {%- if uses_amqp_message %}
        private async Task DispatchMessageAsync(Message amqpMessage, MessageContext context)
        {
            var messageSubject = amqpMessage.ApplicationProperties["subject"]?.ToString();
            _logger.LogInformation($"Dispatching message with subject {messageSubject}");
            switch (messageSubject)
            {
                {% for messageid, message in messagegroup.messages.items() -%}
                {%- set messagename = messageid | strip_dots | pascal -%}
                {%- set message_body_type = util.body_type(data_project_name, root, message) -%}
                case "{{ messageid }}":
                    if ({{ messagename }}Async != null)
                    {
                        var tasks = new List<Task>();
                        foreach (var handler in {{ messagename }}Async.GetInvocationList())
                        {
                            if ( handler == null ) continue;
                            {%- if message_body_type != "byte[]" -%}
                            var t = (Task?)(handler.DynamicInvoke(
                                amqpMessage,
                                {{ message_body_type }}.FromData(amqpMessage.Body, amqpMessage.Properties.ContentType),
                                context));
                            if (t != null)
                            {
                                tasks.Add(t);
                            }
                            {%- else -%}
                            var t = (Task?)(handler.DynamicInvoke(amqpMessage, amqpMessage.Body, context));
                            if (t != null)
                            {
                                tasks.Add(t);
                            }
                            {%- endif -%}
                        }
                        await Task.WhenAll(tasks);
                    }
                    return;
                {% endfor %}
                default:
                    if (UnhandledMessageAsync != null)
                    {
                        await UnhandledMessageAsync(amqpMessage, context);
                    }
                    break;
            }
        }
        {%- endif %}

        /// <summary>
        /// Disposes the consumer synchronously.
        /// </summary>
        public void Dispose()
        {
            _endpoint.Dispose();
        }

        /// <summary>
        /// Disposes the consumer asynchronously, ensuring proper cleanup.
        /// </summary>
        public async ValueTask DisposeAsync()
        {
            await StopAsync();
            _endpoint?.Dispose();
        }

        {%- if root.endpoints -%}
        {%- for endpointid, endpoint in root.endpoints.items() -%}
        {%- if endpoint.usage and "consumer" in endpoint.usage -%}
        {%- set protocol = endpoint.protocol | lower -%}
        {%- set options = endpoint.protocoloptions -%}
        {%- set endpoints = endpoint.endpoints %}
        {%- set isCloudEvent = endpoint.format and endpoint.format.lower().startswith("cloudevents") -%}
        {%- set is_amqp = endpoint.binding and endpoint.binding.lower().startswith("amqp") %}

        public static {{ class_name }} CreateFor{{ endpointid | pascal | strip_namespace }}(EndpointCredential credential, ILoggerFactory? loggerFactory)
        {
            var options = new Dictionary<string, string>
            {
                {%- for key, value in options.items() %}
                { "{{ key }}", "{{ value }}" }{%- if not loop.last %}, {% endif %}
                {%- endfor %}
            };

            var endpoints = new List<Uri>
            {
                {%- for epo in endpoints %}
                new Uri("{{ epo.uri }}"){%- if not loop.last %}, {% endif %}
                {%- endfor %}
            };

            var endpoint = new AmqpConsumer(credential, endpoints.First(), options, loggerFactory);
            return new {{ class_name }}(endpoint, loggerFactory);
        }
        {%- endif %}
        {%- endfor %}
        {%- endif %}
    }

    /// <summary>
    /// Builder for creating {{ class_name }} instances with fluent configuration.
    /// </summary>
    public class {{ class_name }}Builder
    {
        private EndpointCredential? _credential;
        private Uri? _endpoint;
        private ILoggerFactory? _loggerFactory;
        private Dictionary<string, string> _options = new Dictionary<string, string>();

        /// <summary>
        /// Sets the AMQP authentication credential.
        /// </summary>
        public {{ class_name }}Builder WithCredential(EndpointCredential credential)
        {
            _credential = credential ?? throw new ArgumentNullException(nameof(credential));
            return this;
        }

        /// <summary>
        /// Sets the AMQP broker endpoint URI.
        /// </summary>
        public {{ class_name }}Builder WithEndpoint(string uri)
        {
            if (string.IsNullOrWhiteSpace(uri))
                throw new ArgumentException("URI cannot be null or empty", nameof(uri));
            _endpoint = new Uri(uri);
            return this;
        }

        /// <summary>
        /// Sets the AMQP broker endpoint URI.
        /// </summary>
        public {{ class_name }}Builder WithEndpoint(Uri uri)
        {
            _endpoint = uri ?? throw new ArgumentNullException(nameof(uri));
            return this;
        }

        /// <summary>
        /// Sets the logger factory for diagnostics.
        /// </summary>
        public {{ class_name }}Builder WithLogger(ILoggerFactory loggerFactory)
        {
            _loggerFactory = loggerFactory;
            return this;
        }

        /// <summary>
        /// Sets the AMQP node (queue/topic) name.
        /// </summary>
        public {{ class_name }}Builder WithNode(string node)
        {
            if (string.IsNullOrWhiteSpace(node))
                throw new ArgumentException("Node cannot be null or empty", nameof(node));
            _options["node"] = node;
            return this;
        }

        /// <summary>
        /// Adds a custom protocol option.
        /// </summary>
        public {{ class_name }}Builder WithOption(string key, string value)
        {
            if (string.IsNullOrWhiteSpace(key))
                throw new ArgumentException("Key cannot be null or empty", nameof(key));
            _options[key] = value;
            return this;
        }

        /// <summary>
        /// Builds the consumer instance.
        /// </summary>
        /// <exception cref="InvalidOperationException">Thrown if required configuration is missing.</exception>
        public {{ class_name }} Build()
        {
            if (_credential == null)
                throw new InvalidOperationException("Credential is required. Call WithCredential().");
            if (_endpoint == null)
                throw new InvalidOperationException("Endpoint is required. Call WithEndpoint().");
            
            return {{ class_name }}.CreateConsumer(_credential, _endpoint, _loggerFactory, _options);
        }
    }
    {% endfor %}
}
