using EonaCat.LogStack.Core;
using EonaCat.LogStack.DeadLettering;
using System;
using System.Collections.Generic;
using System.Threading;
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.Flows;

/// <summary>
/// Dead Letter Queue Flow - captures log events that failed to be delivered to other flows.
/// Enables analysis and replay of failed logs.
/// </summary>
public class DlqFlow : FlowBase
{
    private readonly DeadLetterQueue _dlq;
    private readonly List<IFlow> _backupFlows = new List<IFlow>();
    private readonly object _flowsLock = new object();
    private int _disposed;

    public DlqFlow(
        string name = "DLQ",
        LogLevel minimumLevel = LogLevel.Warning,
        int maxCapacity = 100000)
        : base(name, minimumLevel)
    {
        _dlq = new DeadLetterQueue(maxCapacity);
    }

    /// <summary>
    /// Gets the underlying dead letter queue for inspection and replay.
    /// </summary>
    public DeadLetterQueue GetDeadLetterQueue() => _dlq;

    /// <summary>
    /// Adds a backup flow to attempt retrying failed events.
    /// </summary>
    public DlqFlow AddBackupFlow(IFlow flow)
    {
        if (flow == null) throw new ArgumentNullException(nameof(flow));
        lock (_flowsLock)
        {
            _backupFlows.Add(flow);
        }
        return this;
    }

    /// <summary>
    /// Blasts a single log event to the DLQ.
    /// </summary>
    public override async Task<WriteResult> BlastAsync(LogEvent logEvent, CancellationToken cancellationToken = default)
    {
        if (_disposed != 0)
            return WriteResult.Dropped;

        if (!IsLogLevelEnabled(logEvent))
            return WriteResult.Success;

        // Try backup flows first before enqueueing to DLQ
        List<IFlow> backupFlows;
        lock (_flowsLock)
        {
            backupFlows = new List<IFlow>(_backupFlows);
        }

        bool anyBackupSucceeded = false;
        var failureReasons = new List<string>();

        foreach (var backupFlow in backupFlows)
        {
            try
            {
                var result = await backupFlow.BlastAsync(logEvent, cancellationToken).ConfigureAwait(false);
                if (result == WriteResult.Success)
                {
                    anyBackupSucceeded = true;
                    break;
                }
                failureReasons.Add($"Backup flow '{backupFlow.Name}': failed");
            }
            catch (Exception ex)
            {
                failureReasons.Add($"Backup flow '{backupFlow.Name}' exception: {ex.Message}");
            }
        }

        if (anyBackupSucceeded)
        {
            BlastedCount++;
            return WriteResult.Success;
        }

        // All backups failed, enqueue to DLQ
        var failureReason = string.Join("; ", failureReasons);
        if (failureReason.Length > 500)
            failureReason = failureReason.Substring(0, 500) + "...";

        var enqueued = _dlq.Enqueue(logEvent, failureReason ?? "Unknown failure");

        if (enqueued)
        {
            BlastedCount++;
            return WriteResult.Success;
        }

        DroppedCount++;
        return WriteResult.Dropped;
    }

    /// <summary>
    /// Blasts a batch of log events to the DLQ.
    /// </summary>
    public override async Task<WriteResult> BlastBatchAsync(ReadOnlyMemory<LogEvent> logEvents, CancellationToken cancellationToken = default)
    {
        if (_disposed != 0)
            return WriteResult.Dropped;

        var events = logEvents.ToArray();
        int successCount = 0;

        foreach (var logEvent in events)
        {
            var result = await BlastAsync(logEvent, cancellationToken).ConfigureAwait(false);
            if (result == WriteResult.Success)
                successCount++;
        }

        return successCount > 0 ? WriteResult.Success : WriteResult.Dropped;
    }

    /// <summary>
    /// Flushes the DLQ (does nothing, but required by interface).
    /// </summary>
    public override Task FlushAsync(CancellationToken cancellationToken = default)
    {
        return Task.CompletedTask;
    }

    /// <summary>
    /// Gets statistics about this DLQ flow.
    /// </summary>
    public DeadLetterQueueStats GetStatistics()
    {
        return _dlq.GetStats();
    }

    /// <summary>
    /// Attempts to replay queued dead letters to backup flows.
    /// </summary>
    public async Task<int> ReplayOldestAsync(int count)
    {
        List<IFlow> backupFlows;
        lock (_flowsLock)
        {
            backupFlows = new List<IFlow>(_backupFlows);
        }

        return await _dlq.ReplayOldestAsync(count, (IEnumerable<IFlow>)backupFlows).ConfigureAwait(false);
    }

    /// <summary>
    /// Gets all dead letter events.
    /// </summary>
    public List<DeadLetterEvent> GetDeadLetters(int skip = 0, int take = 100)
    {
        return _dlq.GetAll(skip, take);
    }

    /// <summary>
    /// Clears all dead letters from the queue.
    /// </summary>
    public int ClearDeadLetters()
    {
        return _dlq.Clear();
    }

    public override async ValueTask DisposeAsync()
    {
        if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0)
            return;

        List<IFlow> flowsToDispose;
        lock (_flowsLock)
        {
            flowsToDispose = new List<IFlow>(_backupFlows);
            _backupFlows.Clear();
        }

        foreach (var flow in flowsToDispose)
        {
            try
            {
                await flow.DisposeAsync().ConfigureAwait(false);
            }
            catch { }
        }

        await base.DisposeAsync().ConfigureAwait(false);
    }
}
