using EonaCat.LogStack.Core;
using EonaCat.LogStack.Flows;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.

namespace EonaCat.LogStack.DeadLettering;

/// <summary>
/// Dead Letter Queue (DLQ) for capturing failed log attempts and enabling replay.
/// Superior to data loss as it preserves logs that couldn't be delivered to any flow.
/// </summary>
public class DeadLetterQueue
{
    private readonly ConcurrentQueue<DeadLetterEvent> _queue = new ConcurrentQueue<DeadLetterEvent>();
    private readonly int _maxCapacity;
    private int _currentCount;
    private long _totalDropped;
    private long _totalEnqueued;
    private readonly object _statsLock = new object();

    public event EventHandler<DeadLetterEvent> EventEnqueued;
    public event EventHandler<DeadLetterEventReplayed> EventReplayed;

    public DeadLetterQueue(int maxCapacity = 100000)
    {
        _maxCapacity = Math.Max(1000, maxCapacity);
    }

    /// <summary>
    /// Enqueues a failed log event with the reason for failure.
    /// </summary>
    public bool Enqueue(LogEvent logEvent, string failureReason, Exception failureException = null)
    {

        if (_currentCount >= _maxCapacity)
        {
            lock (_statsLock)
            {
                _totalDropped++;
            }
            return false;
        }

        var dlEvent = new DeadLetterEvent
        {
            LogEvent = logEvent,
            EnqueuedAt = DateTime.UtcNow,
            FailureReason = failureReason ?? "Unknown",
            FailureException = failureException?.ToString(),
            RetryCount = 0
        };

        _queue.Enqueue(dlEvent);
        _currentCount++;

        lock (_statsLock)
        {
            _totalEnqueued++;
        }

        EventEnqueued?.Invoke(this, dlEvent);
        return true;
    }

    /// <summary>
    /// Gets the count of events in the DLQ.
    /// </summary>
    public int GetCount() => _currentCount;

    /// <summary>
    /// Peeks at the next event without removing it.
    /// </summary>
    public bool TryPeek(out DeadLetterEvent dlEvent)
    {
        return _queue.TryPeek(out dlEvent);
    }

    /// <summary>
    /// Dequeues the next event.
    /// </summary>
    public bool TryDequeue(out DeadLetterEvent dlEvent)
    {
        if (_queue.TryDequeue(out var evt))
        {
            _currentCount--;
            dlEvent = evt;
            return true;
        }

        dlEvent = null;
        return false;
    }

    /// <summary>
    /// Gets all events in the DLQ without removing them.
    /// </summary>
    public List<DeadLetterEvent> GetAll(int skip = 0, int take = 100)
    {
        return _queue.Skip(skip).Take(take).ToList();
    }

    /// <summary>
    /// Replays a dead letter event to specified flows.
    /// </summary>
    public async Task<bool> ReplayAsync(DeadLetterEvent dlEvent, IEnumerable<IFlow> targetFlows)
    {
        if (dlEvent == null)
            throw new ArgumentNullException(nameof(dlEvent));

        dlEvent.RetryCount++;
        dlEvent.LastRetryAt = DateTime.UtcNow;

        var flowsList = targetFlows.ToList();
        var tasks = new List<Task<WriteResult>>();

        foreach (var flow in flowsList)
        {
            tasks.Add(flow.BlastAsync(dlEvent.LogEvent));
        }

        var results = await Task.WhenAll(tasks).ConfigureAwait(false);
        var success = results.Any(r => r == WriteResult.Success);

        if (success)
        {
            dlEvent.ReplayedAt = DateTime.UtcNow;
            dlEvent.ReplaySuccessful = true;
            EventReplayed?.Invoke(this, new DeadLetterEventReplayed
            {
                Event = dlEvent,
                WasSuccessful = true,
                FlowsTargeted = flowsList.Count
            });
        }

        return success;
    }

    /// <summary>
    /// Bulk replay of oldest events (useful for periodic retry jobs).
    /// </summary>
    public async Task<int> ReplayOldestAsync(int count, IEnumerable<IFlow> targetFlows)
    {
        var toReplay = GetAll(0, count);
        int successCount = 0;

        foreach (var dlEvent in toReplay)
        {
            if (await ReplayAsync(dlEvent, targetFlows))
            {
                successCount++;
                // Remove from queue if successful
                _currentCount--;
            }
        }

        return successCount;
    }

    /// <summary>
    /// Clears all events from the DLQ.
    /// </summary>
    public int Clear()
    {
        var count = _currentCount;
        while (_queue.TryDequeue(out _))
        {
            _currentCount--;
        }
        return count;
    }

    /// <summary>
    /// Gets statistics about the DLQ.
    /// </summary>
    public DeadLetterQueueStats GetStats()
    {
        lock (_statsLock)
        {
            return new DeadLetterQueueStats
            {
                CurrentQueueSize = _currentCount,
                MaxCapacity = _maxCapacity,
                TotalEnqueued = _totalEnqueued,
                TotalDropped = _totalDropped,
                QueueUtilizationPercent = (_currentCount * 100.0) / _maxCapacity,
                OldestEventAge = _queue.TryPeek(out var oldest) 
                    ? DateTime.UtcNow - oldest.EnqueuedAt 
                    : TimeSpan.Zero
            };
        }
    }

    /// <summary>
    /// Gets events grouped by failure reason.
    /// </summary>
    public Dictionary<string, int> GetFailureReasonStats()
    {
        var stats = new Dictionary<string, int>();
        foreach (var evt in _queue)
        {
            var reason = evt.FailureReason ?? "Unknown";
            if (!stats.ContainsKey(reason))
                stats[reason] = 0;
            stats[reason]++;
        }
        return stats.OrderByDescending(x => x.Value).ToDictionary(x => x.Key, x => x.Value);
    }
}

/// <summary>
/// Represents a dead letter event.
/// </summary>
public class DeadLetterEvent
{
    /// <summary>
    /// The original log event that failed to be delivered.
    /// </summary>
    public LogEvent LogEvent { get; set; }

    /// <summary>
    /// When this event was enqueued to the DLQ.
    /// </summary>
    public DateTime EnqueuedAt { get; set; }

    /// <summary>
    /// Reason why the log event failed.
    /// </summary>
    public string FailureReason { get; set; }

    /// <summary>
    /// Exception details if available.
    /// </summary>
    public string FailureException { get; set; }

    /// <summary>
    /// Number of times this event has been retried.
    /// </summary>
    public int RetryCount { get; set; }

    /// <summary>
    /// When this event was last retried.
    /// </summary>
    public DateTime? LastRetryAt { get; set; }

    /// <summary>
    /// When this event was successfully replayed (if at all).
    /// </summary>
    public DateTime? ReplayedAt { get; set; }

    /// <summary>
    /// Whether the replay was successful.
    /// </summary>
    public bool ReplaySuccessful { get; set; }
}

/// <summary>
/// Event raised when a dead letter event is replayed.
/// </summary>
public class DeadLetterEventReplayed
{
    public DeadLetterEvent Event { get; set; }
    public bool WasSuccessful { get; set; }
    public int FlowsTargeted { get; set; }
}

/// <summary>
/// Statistics about the dead letter queue.
/// </summary>
public class DeadLetterQueueStats
{
    /// <summary>
    /// Current number of events in the queue.
    /// </summary>
    public int CurrentQueueSize { get; set; }

    /// <summary>
    /// Maximum capacity of the queue.
    /// </summary>
    public int MaxCapacity { get; set; }

    /// <summary>
    /// Total events ever enqueued.
    /// </summary>
    public long TotalEnqueued { get; set; }

    /// <summary>
    /// Total events dropped due to capacity.
    /// </summary>
    public long TotalDropped { get; set; }

    /// <summary>
    /// Queue utilization as percentage.
    /// </summary>
    public double QueueUtilizationPercent { get; set; }

    /// <summary>
    /// Age of the oldest event in queue.
    /// </summary>
    public TimeSpan OldestEventAge { get; set; }
}
