{%- 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")) %}
{%- set uses_mqtt_message = (root | exists( "protocol", "mqtt")) %}
{%- set uses_mqtt_endpoint = (root | exists("protocol", "mqtt")) %}
// 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.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Threading;
using Xunit;
using Microsoft.Extensions.Logging;
using MQTTnet;
using MQTTnet.Client;
using DotNet.Testcontainers.Containers;
using DotNet.Testcontainers.Builders;

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_cloudevents_message %}
using CloudNative.CloudEvents;
using CloudNative.CloudEvents.SystemTextJson;
{%- endif %}

namespace {{ project_name | pascal }}.Test
{
    public class {{ project_name | pascal | strip_dots}}Fixture : IAsyncLifetime
    {
        public IContainer? MosquittoContainerInstance { get; protected set; }
        public string? MosquittoBrokerHost { get; protected set; }
        public int MosquittoBrokerPort { get; protected set; }
        private ILoggerFactory _loggerFactory;
        private ILogger _logger;

        public ILoggerFactory GetLoggerFactory()
        {
            return _loggerFactory;
        }

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

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

        private string? mosquittoConfigFilePath = null;
        private string? mosquittoPasswdPath = null;
        private static string mosquittoConfig =
                        "listener 1883\n" +
                        "log_timestamp true\n" +
                        "log_timestamp_format %d-%m-%YT%H:%M:%S\n" +
                        "persistence false\n" +
                        "allow_anonymous true\n" +
                        "password_file /mosquitto/config/mosquitto.passwd\n";
        private static string mosquittoPasswd = "test:$7$101$R3gkC7cVgL8X9vKv$xPZ10eJS1RCp7DE4A2ZHQVRmDP1oB6hV8gFIeyIw7oPNAIO6WaXaKcRaRtiSTeqoKMnrnTR9zKKGaoV3acXlHg==";
        
        public async Task InitializeAsync()
        {
            
            try
            {
                mosquittoConfigFilePath = Path.GetTempFileName();
                File.WriteAllText(mosquittoConfigFilePath, mosquittoConfig);
                mosquittoPasswdPath = Path.GetTempFileName();
                File.WriteAllText(mosquittoPasswdPath, mosquittoPasswd);

                MosquittoContainerInstance = new ContainerBuilder()
                    .WithImage("eclipse-mosquitto:2.0")
                    .WithBindMount(mosquittoConfigFilePath, "/mosquitto/config/mosquitto.conf")
                    .WithBindMount(mosquittoPasswdPath, "/mosquitto/config/mosquitto.passwd")
                    .WithPortBinding(1883, true)
                    .Build();

                await MosquittoContainerInstance.StartAsync();
                _logger.LogDebug("Mosquitto container started.");

                MosquittoBrokerHost = MosquittoContainerInstance.Hostname;
                MosquittoBrokerPort = MosquittoContainerInstance.GetMappedPublicPort(1883);
                _logger.LogDebug($"Mosquitto broker address: {MosquittoBrokerHost}:{MosquittoBrokerPort}");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "An error occurred during InitializeAsync");
                throw;
            }
        }

        public async Task DisposeAsync()
        {
            try
            {
                if (MosquittoContainerInstance != null)
                {
                    await MosquittoContainerInstance.StopAsync();
                }
                if (mosquittoConfigFilePath != null && File.Exists(mosquittoConfigFilePath))
                {
                    File.Delete(mosquittoConfigFilePath);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"An error occurred during DisposeAsync: {ex.Message}");
            }
        }
    }

    [CollectionDefinition("MQTT broker")]
    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 producer_class_name = "global::" + ( pascal_group_name | concat_namespace(project_name) | pascal ) + '.' + ( pascal_group_name | strip_namespace ) + "Producer" %}
    {%- set dispatcher_class_name = "global::" + ( pascal_group_name | concat_namespace(project_name) | pascal ) + '.' + ( pascal_group_name | strip_namespace ) + "Dispatcher" %}
    
    {%- set producer_test_class_name = ( project_name | strip_dots | pascal )+( pascal_group_name | strip_dots )+"ProducerTests" %}
    [Collection("MQTT broker")]
    public class {{ producer_test_class_name }} 
    {
        private readonly {{ project_name | pascal | strip_dots}}Fixture _fixture;
        private readonly ILogger _logger;
        private readonly {{ producer_class_name }} _producer;
        private readonly {{ dispatcher_class_name }} _dispatcher;
        private readonly MqttSubscriber _subscriber;

        public {{ producer_test_class_name }}({{ project_name | pascal | strip_dots }}Fixture fixture)
        {
            _fixture = fixture;
            _logger = _fixture.GetLoggerFactory().CreateLogger<{{ producer_test_class_name }}>();
            var options = new MqttClientOptionsBuilder()
                .WithTcpServer(_fixture.MosquittoBrokerHost, _fixture.MosquittoBrokerPort)
                .WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V500)
                .WithClientId("{{ producer_test_class_name }}Client")
                .WithCredentials("test", "password")
                .WithCleanSession()
                .Build();
            var factory = new MqttFactory();
            var client = factory.CreateMqttClient();
            client.ConnectAsync(options).Wait();
            
            // Use custom topic mappings for dispatcher - all messages are received on the test topic
            var dispatcherTopicMappings = new Dictionary<string, string>
            {
                {%- for messageid, message in messagegroup.messages.items() %}
                { "{{ messageid }}", _fixture.TopicName },
                {%- endfor %}
            };
            
            _producer = new {{ producer_class_name }}(client);
            _subscriber = new MqttSubscriber(client, new string[] { _fixture.TopicName });
            _dispatcher = new {{ dispatcher_class_name }}(_subscriber, dispatcherTopicMappings, _fixture.GetLoggerFactory());
            _subscriber.SubscribeAsync().GetAwaiter().GetResult();
        }

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

        {%- for messageid, message in messagegroup.messages.items() %}
        {%- set messagename = messageid | strip_namespace | pascal %}
        {%- set isCloudEvent = cloudEvents.isCloudEvent(message) %}
        {%- set isMqtt = (message | exists( "protocol", "mqtt" )) %}
        [Fact]
        public async Task Test{{ messagename }}Message()
        {
            _logger.LogInformation("Starting Test{{ messagename }}Message");
            try
            {   
                {%- set message_body_type = util.body_type(data_project_name, root, message) -%}
                var tcs = new TaskCompletionSource<bool>();
                var receivedCount = 0;
                var receivedMessages = new System.Collections.Concurrent.ConcurrentBag<{{ message_body_type }}>();
                
                _dispatcher.{{ messagename }}Async += (mqtt, {% if isCloudEvent %}ce, {% endif %}data, topicParams) =>
                {
                    receivedMessages.Add(data);
                    Interlocked.Increment(ref receivedCount);
                    if (receivedCount >= 5)
                    {
                        tcs.SetResult(true);
                    }
                    return Task.CompletedTask;
                };
                {%- if isCloudEvent %}
                {%- set uriargs = cloudEvents.DeclareUriTemplateArguments(message) -%}
                {%- elif isMqtt %}
                {%- set uriargs = mqtt.DeclareUriTemplateArguments(message) -%}
                {%- endif %}
                {%- if message_body_type != "byte[]" %}
                var eventDataTest = new {{ message_body_type }}Tests();
                var eventDataInstance = eventDataTest.CreateInstance();
                for (int i = 0; i < 5; i++)
                {
                    await _producer.Send{{ messagename }}Async(eventDataInstance
                    {%- if uriargs -%}
                    {%- for arg in uriargs.split(',') if arg.strip() -%}
                    , {{ arg.strip().split(' ')[1] -}}: "test"
                    {%- endfor -%}
                    {%- endif -%}
                    , topic: _fixture.TopicName
                    );
                }
                {%- else %}
                for (int i = 0; i < 5; i++)
                {
                    await _producer.Send{{ messagename }}Async(new byte[0]
                    {%- if uriargs -%}
                    {%- for arg in uriargs.split(',') if arg.strip() -%}
                    , {{ arg.strip().split(' ')[1] -}}: "test"
                    {%- endfor -%}
                    {%- endif -%}
                    , topic: _fixture.TopicName
                    );
                }
                {%- endif %}
#pragma warning disable xUnit1031 // Do not use blocking task operations in test method
                Assert.True(Task.WaitAll([tcs.Task], 15000));
#pragma warning restore xUnit1031 // Do not use blocking task operations in test method
                Assert.Equal(5, receivedCount);
                Assert.Equal(5, receivedMessages.Count);
                _logger.LogInformation("Test event sent");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "An error occurred during Test{{ messagename }}Message");
                throw;
            }
        }
        {%- endfor %}
    }
    {%- endfor %}
}
