using e_suite.API.Common.extensions;
using e_suite.MessageProcessor.Handlers;
using RabbitMQ.Client.Events;
using RabbitMQ.Client;
using e_suite.Messaging.Common;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using e_suite.MessageProcessor.Extensions;
using e_suite.API.Common;
using Autofac;
using RabbitMQ.Client.Exceptions;

namespace e_suite.MessageProcessor.QueueProcessor;

public class QueueProcessorService : IHostedService
{
    private readonly ILifetimeScope _lifetimeScope;
    private readonly IConnection _connection;
    private readonly ILogger _logger;
    private readonly IExceptionLogManager _exceptionLogManager;

    public QueueProcessorService(ILifetimeScope lifetimeScope, IConfiguration configuration, ILogger logger, IRabbitMqConnectionFactory connectionFactory, IExceptionLogManager exceptionLogManager)
    {
        _lifetimeScope = lifetimeScope;
        _logger = logger;
        _exceptionLogManager = exceptionLogManager;

        connectionFactory.HostName = configuration.GetConfigValue("RABBITMQ_HOSTNAME", "RabbitMQ:hostname", "localhost")!;

        if (!string.Equals(connectionFactory.HostName, "localhost", StringComparison.InvariantCultureIgnoreCase)
            && !string.Equals(connectionFactory.HostName, "host.docker.internal", StringComparison.InvariantCultureIgnoreCase))
        {
            connectionFactory.VirtualHost = configuration.GetConfigValue("RABBITMQ_VHOST", "RabbitMQ:vhost", "")!;
            connectionFactory.UserName = configuration.GetConfigValue("RABBITMQ_USER", "RabbitMQ:user", "")!;
            connectionFactory.Password = configuration.GetConfigValue("RABBITMQ_PASSWORD", "RabbitMQ:password", "")!;
        }

        _connection = connectionFactory.CreateConnection();
    }

    private List<IChannel> Channels { get; } = [];

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is starting.");

        Channels.Add(await ListenForMessagesOnQueue<DatabaseQueueHandler>(_connection, MessageQueueNames.Database));
        Channels.Add(await ListenForMessagesOnQueue<SigmaImportQueueHandler>(_connection, MessageQueueNames.SigmaImport));
        Channels.Add(await ListenForMessagesOnQueue<EFlowImportQueueHandler>(_connection, MessageQueueNames.EFlowSync));
        Channels.Add(await ListenForMessagesOnQueue<ActivityQueueHandler>(_connection, MessageQueueNames.Activity));
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is stopping.");

        foreach (var channel in Channels)
            channel.Dispose();

        _connection.Dispose();

        return Task.CompletedTask;
    }


    public async Task<IChannel> ListenForMessagesOnQueue<T>(IConnection connection, string queueName) where T : QueueHandlerBase
    {
        var channel = await connection.CreateChannelAsync();
        await channel.QueueDeclareAsync(queue: queueName,
            durable: false,
            exclusive: false,
            autoDelete: false,
            arguments: null);
        var consumer1 = new AsyncEventingBasicConsumer(channel);
        consumer1.ReceivedAsync += async (sender, ea) =>
        {
            if (sender is not AsyncEventingBasicConsumer basicConsumer)
                throw new NullReferenceException("channel cannot be null here!");
            try
            {
                await basicConsumer.Channel.BasicAckAsync(ea.DeliveryTag, false);
                _logger.LogInformation("Message Acknowledged");

                await using var messageLifetimeScope = _lifetimeScope.BeginLifetimeScope();
                var queueHandler = messageLifetimeScope.Resolve<T>();
                await queueHandler.OnReceived(basicConsumer.Channel.CurrentQueue!, ea);
            }
            catch (Exception ex)
            {
                _logger.LogError("Error: {Message}", ex.Message);
                if (ea.Redelivered)
                {
                    var supportingData = new
                    {
                        ea.BasicProperties,
                        ea.Body,
                        ea.ConsumerTag,
                        ea.DeliveryTag,
                        ea.Exchange,
                        ea.RoutingKey
                    }.ToJson();

                    await _exceptionLogManager.LogException(ex, "e-suite.MessageProcessor", supportingData, CancellationToken.None);
                    await basicConsumer.Channel.BasicAckAsync(ea.DeliveryTag, false);
                    return;
                }

                try
                {
                    await basicConsumer.Channel.BasicNackAsync(ea.DeliveryTag, false, true);
                }
                catch (AlreadyClosedException)
                {
                    //Do nothing here RabbitMq has already shut things down.
                }
            }
        };
        await channel.BasicConsumeAsync(queue: queueName,
            autoAck: false,
            consumer: consumer1);

        channel.ChannelShutdownAsync += (sender, args) =>
        {
            _logger.LogWarning("Channel shutdown: {Reason}", args.ReplyText);
            return Task.CompletedTask;
        };


        return channel;
    }
}