{%- import "cloudevents.jinja.include" as cloudEvents %}
{%- import "kafka.jinja.include" as kafka %}
{%- import "util.jinja.include" as util -%}
{{ util.CommonFileHeader() }}
{%- set messagegroups = root.messagegroups %}
{%- set uses_ce_message = (root | exists("envelope","CloudEvents/1.0")) %}
{%- set uses_plain_kafka_message = (root | exists( "protocol", "kafka" )) %}
{%- set uses_kafka_endpoint = (root | exists( "protocol", "kafka" )) %}
// This code was generated by the xRegistry tool.
// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.

#nullable enable

using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using Confluent.Kafka;
using Confluent.Kafka.Admin;
using Xunit;
using Microsoft.Extensions.Logging;
using Testcontainers.Kafka;
using DotNet.Testcontainers.Containers;
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Networks;

using {{ project_name | pascal }};
{%- for messagegroupid, messagegroup in messagegroups.items() %}
{%- set pascal_group_name = messagegroupid | pascal %}
using {{ project_name | pascal }}.{{ pascal_group_name | pascal  }};
{%- endfor %}
{%- if uses_ce_message %}
{{ kafka.CloudEventsUsings() }}
{%- endif %}

namespace {{ project_name | pascal }}.Test
{
    public class {{ project_name | pascal | strip_dots}}Fixture : IAsyncLifetime
    {
        public KafkaContainer? KafkaContainerInstance { get; protected set; }
        public string? KafkaBootstrapServers { get; protected set; }
        private ILoggerFactory _loggerFactory;
        private ILogger _logger;

        public ILoggerFactory GetLoggerFactory()
        {
            return _loggerFactory;
        }

        public string TopicName 
        {
            get
            {
                return "testttopic";
            }
        }

        public {{ project_name | pascal | strip_dots}}Fixture()
        {
            _loggerFactory = LoggerFactory.Create(builder =>
            {
                builder.AddDebug().AddConsole();
            });
            _logger = _loggerFactory.CreateLogger<{{ project_name | pascal | strip_dots}}Fixture>();
        }

        public async Task InitializeAsync()
        {
            try
            {
                _logger.LogDebug("Initializing Kafka test fixture.");
                
                KafkaContainerInstance = new KafkaBuilder()
                        .Build();

                await KafkaContainerInstance.StartAsync();
                _logger.LogDebug("Kafka container started.");

                KafkaBootstrapServers = KafkaContainerInstance.GetBootstrapAddress();
                _logger.LogDebug($"Kafka bootstrap servers: {KafkaBootstrapServers}");

                await CreateKafkaTopic(TopicName, KafkaContainerInstance.GetBootstrapAddress());
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "An error occurred during InitializeAsync");
                throw;
            }
        }

        public async Task DisposeAsync()
        {
            try
            {
                _logger.LogDebug("Disposing Kafka test fixture.");
                
                if (KafkaContainerInstance != null)
                {
                    await KafkaContainerInstance.StopAsync();
                    _logger.LogDebug("Kafka container stopped.");
                }
               }
            catch (Exception ex)
            {
                Console.WriteLine($"An error occurred during DisposeAsync: {ex.Message}");
            }
        }

        private static async Task CreateKafkaTopic(string topicName, string bootstrapServers)
        {
            using var adminClient = 
                new AdminClientBuilder(new AdminClientConfig { BootstrapServers = bootstrapServers }).Build();

            await adminClient.CreateTopicsAsync(new TopicSpecification[]
            {
                new() { Name = topicName, ReplicationFactor = 1, NumPartitions = 1 }
            });
        }

    }

    [CollectionDefinition("Kafka container")]
    public class {{ project_name | pascal | strip_dots}}CollectionFixture : ICollectionFixture<{{ project_name | pascal | strip_dots}}Fixture>
    {
    }

    {%- for messagegroupid, messagegroup in messagegroups.items() %}
    {%- set pascal_group_name = messagegroupid | pascal %}
    {%- set class_name = (pascal_group_name | strip_namespace) + "EventDispatcher" %}
    {%- set test_class_name = ( project_name | strip_dots | pascal )+( pascal_group_name | strip_dots )+"Tests" %}
    [Collection("Kafka container")]
    public class {{ test_class_name }} 
    {
        private readonly {{ project_name | pascal | strip_dots}}Fixture _fixture;
        private readonly ILogger _logger;

        public {{ test_class_name }}({{ project_name | pascal | strip_dots}}Fixture fixture)
        {
            _fixture = fixture;
            _logger = _fixture.GetLoggerFactory().CreateLogger<{{ test_class_name }}>();
        }

#pragma warning disable CS8604 // init takes care of nullables 

        {%- for messageid, message in messagegroup.messages.items() %}
        {%- set messagename = messageid | strip_namespace | pascal %}
        {%- set message_body_type = util.body_type(data_project_name, root, message) %}
        [Fact]
        public async Task Test{{ messagename }}Message()
        {
            _logger.LogInformation("Starting Test{{ messagename }}Message");
            try
            {
                var dispatcher = new {{ class_name }}(_fixture.GetLoggerFactory());
                var messageReceived = new TaskCompletionSource<bool>();
                var receivedCount = 0;
                var receivedMessages = new System.Collections.Concurrent.ConcurrentBag<object>();

                dispatcher.{{ messagename }}Async += (message, cloudEvent, messageObj) =>
                {
                    try
                    {
                        _logger.LogInformation("{{ messagename }} message received");
                        Assert.NotNull(message);
                        Assert.NotNull(cloudEvent);
                        {%- if message_body_type != "byte[]" %}
                        Assert.NotNull(messageObj);
                        receivedMessages.Add(messageObj);
                        {%- else %}
                        receivedMessages.Add(message);
                        {%- endif %}
                        Interlocked.Increment(ref receivedCount);
                        if (receivedCount >= 5)
                        {
                            messageReceived.SetResult(true);
                        }
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(ex, "An error occurred during {{ messagename }} message processing");
                        messageReceived.SetResult(false);
                    }
                    return Task.CompletedTask;
                };

                var consumerConfig = new ConsumerConfig
                {
                    BootstrapServers = _fixture.KafkaBootstrapServers,
                    GroupId = "test-group",
                    AutoOffsetReset = AutoOffsetReset.Earliest
                };

                var processor = dispatcher.CreateKafkaProcessor(consumerConfig, _fixture.TopicName);
                await processor.StartAsync();
                _logger.LogDebug("Kafka processor started.");

                var producerConfig = new ProducerConfig { BootstrapServers = _fixture.KafkaBootstrapServers };
                using var producer = new ProducerBuilder<byte[], byte[]>(producerConfig).Build();
                {%- if message_body_type != "byte[]" %}
                var eventDataTest = new {{ message_body_type }}Tests();
                var eventDataInstance = eventDataTest.CreateInstance();
                var eventData = eventDataInstance.ToByteArray("application/json");
                {%- else %}
                var eventData = System.Text.Encoding.UTF8.GetBytes("Test");
                {%- endif %}

                _logger.LogDebug("Producing 5 test messages to Kafka.");
                for (int i = 0; i < 5; i++)
                {
                    // Create new headers with unique ID for each message
                    var headers = new Headers
                    {
                        new Header("content-type", Encoding.UTF8.GetBytes("application/json")),
                        {%- if "type" in message.envelopemetadata and "value" in message.envelopemetadata["type"] %}
                        new Header("ce_type", Encoding.UTF8.GetBytes("{{ message.envelopemetadata["type"]["value"] }}")),
                        {%- else %}
                        new Header("ce_type", Encoding.UTF8.GetBytes("{{ messageid }}")),
                        {%- endif %}
                        new Header("ce_specversion", Encoding.UTF8.GetBytes("1.0")),
                        new Header("ce_source", Encoding.UTF8.GetBytes("/test")),
                        new Header("ce_id", Encoding.UTF8.GetBytes(Guid.NewGuid().ToString()))
                    };
                    
                    var testMessage = new Message<byte[], byte[]>
                    {
                        Value = eventData,
                        Headers = headers
                    };
                    
                    await producer.ProduceAsync(_fixture.TopicName, testMessage);
                }
                producer.Flush();
                _logger.LogInformation("5 test messages sent");

                var messageReceivedTask = await Task.WhenAny(messageReceived.Task, Task.Delay(15000));
                Assert.True(messageReceived.Task.IsCompleted, "Not all messages were received within the timeout period.");
                Assert.Equal(5, receivedCount);
                Assert.Equal(5, receivedMessages.Count);
                _logger.LogInformation($"All {receivedCount} messages received validation completed");

                await processor.StopAsync();
                _logger.LogDebug("Kafka processor stopped.");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "An error occurred during Test{{ messagename }}Message");
                throw;
            }
        }

        {%- endfor %}
    }
    {%- endfor %}
}
