using adas_core.Application.Services.Interfaces;
using EasyNetQ;
using EasyNetQ.SystemMessages;
using Newtonsoft.Json;
using Serilog;
using System.Text;
using ILogger = Serilog.ILogger;

namespace adas_core.Infrastructure.Utils;

/// <summary>
/// Handles errors that occur during RabbitMQ message consumption and publishes them using the configured publisher service.
/// </summary>
public class RabbitConsumerErrorHandler(IPublisherService publisherService)
{
    private const int MaxRetries = 2;
    private static readonly ILogger Logger = Log.ForContext<RabbitConsumerErrorHandler>();

    /// <summary>
    /// Processes a received message by invoking the next handler in the pipeline, and on failure logs the error,
    /// serializes the message body to a string, and delegates to the retry handler using the message properties,
    /// received info, and the captured exception.
    /// </summary>
    /// <param name="message">The deserialized message whose body is inspected or re-serialized for retry handling.</param>
    /// <param name="receivedInfo">Information about the message receipt, passed to the retry handler.</param>
    /// <param name="next">The asynchronous delegate representing the next step in the processing pipeline.</param>
    public async Task HandleAsync<T>(
            Message<T> message,
            MessageReceivedInfo receivedInfo,
            Func<Task> next)
        {
            try
            {
                await next();
            }
            catch (Exception exception)
            {
                Logger.Error(exception, "Consumer error {Message}", exception.Message);
    
                var properties = message.Properties;
    
                var body = Encoding.UTF8.GetString(
                    message.Body is byte[] bytes
                        ? bytes
                        : Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message.Body)));
    
                HandleRetries(receivedInfo, properties, body, exception);
            }
        }

    private void HandleRetries(
        MessageReceivedInfo receivedInfo,
        MessageProperties properties,
        string body,
        Exception exception)
    {
        try
        {
            var headers = properties.Headers != null
                ? new Dictionary<string, object>(properties.Headers)
                : [];

            var retries = GetRetries(properties);

            // MAX RETRIES → ERROR QUEUE
            if (retries > MaxRetries)
            {
                if (!receivedInfo.Queue.StartsWith("Error"))
                {
                    headers["retries"] = BitConverter.GetBytes(MaxRetries + 1);

                    var errorMsg = CreateErrorMessage(
                        receivedInfo,
                        properties,
                        body,
                        exception,
                        headers
                    );

                    _ = publisherService.SendMessageError(
                        errorMsg,
                        $"Error{receivedInfo.Queue}");
                }

                return;
            }

            // RETRY NORMAL
            headers["retries"] = BitConverter.GetBytes(retries + 1);

            var newProps = new MessageProperties
            {
                DeliveryMode = 2,
                Headers = headers,
                ContentType = properties.ContentType,
                CorrelationId = properties.CorrelationId,
                MessageId = properties.MessageId
            };

            var message = new Message<string>(body, newProps);

            var result = publisherService
                .SendMessage(message, receivedInfo.Queue)
                .GetAwaiter()
                .GetResult();

            if (!result)
                throw new Exception("Requeue failed");
        }
        catch (Exception e)
        {
            Logger.Error(e, "Exception processing rabbit message");
        }
    }

    /// <summary>
    /// Retrieves the retry count from the message properties headers. Returns the deserialized integer value when a "retries" header containing a byte array is present, or 0 when the headers are missing, the key is not found, or the value is not a byte array.
    /// </summary>
    /// <param name="properties">The message properties whose headers are inspected for the "retries" entry.</param>
    /// <returns>The number of retries parsed from the "retries" header, or 0 if the header is absent or not a byte array.</returns>
    private int GetRetries(MessageProperties properties)
        {
            if (properties.Headers != null &&
                properties.Headers.TryGetValue("retries", out var retriesObj) &&
                retriesObj is byte[] bytes)
            {
                return BitConverter.ToInt32(bytes, 0);
            }
    
            return 0;
        }

    /// <summary>
    /// Constructs a new error message that preserves the original message properties and attaches exception details for downstream processing or dead-letter handling.
    /// </summary>
    /// <param name="receivedInfo">The metadata of the original received message, including exchange, routing key, and queue information used to identify the error source.</param>
    /// <param name="originalProperties">The properties of the original message from which delivery mode, content type, correlation id, and message id are copied.</param>
    /// <param name="body">The raw body of the original message that caused the error.</param>
    /// <param name="exception">The exception that was raised, whose message is included in the error payload.</param>
    /// <param name="headers">The headers to associate with the resulting error message.</param>
    /// <returns>A <see cref="Message{Error}"/> containing the constructed error and the propagated message properties.</returns>
    private static Message<Error> CreateErrorMessage(
            MessageReceivedInfo receivedInfo,
            MessageProperties originalProperties,
            string body,
            Exception exception,
            Dictionary<string, object> headers)
        {
            var props = new MessageProperties
            {
                Headers = headers,
                DeliveryMode = originalProperties.DeliveryMode,
                ContentType = originalProperties.ContentType,
                CorrelationId = originalProperties.CorrelationId,
                MessageId = originalProperties.MessageId
            };
    
            var error = new Error(
                body,
                exception.Message,
                receivedInfo.Exchange,
                receivedInfo.RoutingKey,
                receivedInfo.Queue,
                DateTime.UtcNow,
                props
            );
    
            return new Message<Error>(error, props);
        }
}