using EonaCat.LogStack.Core;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace EonaCat.LogStack.Flows;

// 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.

/// <summary>
/// A flow that delegates log events to another logger instance.
/// This enables chaining multiple logger instances together, allowing logs to flow
/// through different logging backends or implementations.
/// </summary>
public sealed class DelegatingLoggerFlow : FlowBase
{
    private readonly EonaCatLogStack _targetLogger;
    private readonly Queue<LogEvent> _delegationQueue = new Queue<LogEvent>();
    private readonly object _queueLock = new object();
    private int _disposed;

    /// <summary>
    /// Creates a new delegating logger flow
    /// </summary>
    /// <param name="targetLogger">The logger to delegate log events to</param>
    /// <param name="minimumLevel">The minimum log level to process</param>
    public DelegatingLoggerFlow(
        EonaCatLogStack targetLogger,
        LogLevel minimumLevel = LogLevel.Trace)
        : base($"DelegatingFlow({targetLogger?.GetType().Name ?? "UnknownLogger"})", minimumLevel)
    {
        _targetLogger = targetLogger ?? throw new ArgumentNullException(nameof(targetLogger));
    }

    /// <summary>
    /// The target logger this flow delegates to
    /// </summary>
    public EonaCatLogStack TargetLogger => _targetLogger;

    /// <summary>
    /// Blasts (sends) a single log event to the target logger
    /// </summary>
    public override async Task<WriteResult> BlastAsync(LogEvent logEvent, CancellationToken cancellationToken = default)
    {
        if (!IsEnabled || !IsLogLevelEnabled(logEvent))
        {
            Interlocked.Increment(ref DroppedCount);
            return WriteResult.LevelFiltered;
        }

        try
        {
            // Queue the event for delegation with cancellation support
            lock (_queueLock)
            {
                if (_disposed != 0)
                {
                    Interlocked.Increment(ref DroppedCount);
                    return WriteResult.Dropped;
                }

                _delegationQueue.Enqueue(logEvent);
            }

            // Delegate to the target logger's logging mechanism
            // This is done asynchronously to prevent blocking
            await Task.Run(() => DelegateLogEvent(logEvent), cancellationToken).ConfigureAwait(false);

            Interlocked.Increment(ref BlastedCount);
            return WriteResult.Success;
        }
        catch (Exception ex)
        {
            Interlocked.Increment(ref DroppedCount);
            System.Diagnostics.Debug.WriteLine($"DelegatingLoggerFlow error: {ex.Message}");
            return WriteResult.Failed;
        }
    }

    /// <summary>
    /// Blasts (sends) a batch of log events to the target logger
    /// </summary>
    public override async Task<WriteResult> BlastBatchAsync(ReadOnlyMemory<LogEvent> logEvents, CancellationToken cancellationToken = default)
    {
        if (!IsEnabled)
        {
            Interlocked.Add(ref DroppedCount, logEvents.Length);
            return WriteResult.LevelFiltered;
        }

        var result = WriteResult.Success;
        var eventsArray = logEvents.ToArray();

        foreach (var logEvent in eventsArray)
        {
            try
            {
                if (IsLogLevelEnabled(logEvent))
                {
                    lock (_queueLock)
                    {
                        if (_disposed == 0)
                        {
                            _delegationQueue.Enqueue(logEvent);
                        }
                    }

                    await Task.Run(() => DelegateLogEvent(logEvent), cancellationToken).ConfigureAwait(false);
                    Interlocked.Increment(ref BlastedCount);
                }
                else
                {
                    Interlocked.Increment(ref DroppedCount);
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine($"DelegatingLoggerFlow batch error: {ex.Message}");
                Interlocked.Increment(ref DroppedCount);
                result = WriteResult.Failed;
            }
        }

        return result;
    }

    /// <summary>
    /// Flushes any pending log events to the target logger
    /// </summary>
    public override async Task FlushAsync(CancellationToken cancellationToken = default)
    {
        Queue<LogEvent> pendingEvents;
        lock (_queueLock)
        {
            if (_delegationQueue.Count == 0)
                return;

            pendingEvents = new Queue<LogEvent>(_delegationQueue);
            _delegationQueue.Clear();
        }

        // Process remaining events
        foreach (var logEvent in pendingEvents)
        {
            try
            {
                DelegateLogEvent(logEvent);
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine($"DelegatingLoggerFlow flush error: {ex.Message}");
            }
        }

        await Task.CompletedTask.ConfigureAwait(false);
    }

    /// <summary>
    /// Gets diagnostic information about this flow
    /// </summary>
    public override FlowDiagnostics GetDiagnostics()
    {
        var diagnostics = base.GetDiagnostics();
        diagnostics.Name = $"{Name} (delegates to {TargetLogger.GetType().Name})";
        return diagnostics;
    }

    private void DelegateLogEvent(LogEvent logEvent)
    {
        if (_disposed != 0)
            return;

        try
        {
            // Delegate by re-logging through the target logger using its public Log methods
            // We reconstruct the logging call with the information from the LogEvent
            var messageStr = logEvent.Message.ToString();

            if (logEvent.Exception != null)
            {
                _targetLogger.Log(logEvent.Level, logEvent.Exception, messageStr);
            }
            else
            {
                _targetLogger.Log(messageStr, logEvent.Level);
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine($"Failed to delegate log to target logger: {ex.Message}");
        }
    }

    /// <summary>
    /// Disposes the flow and clears the delegation queue
    /// </summary>
    public override async ValueTask DisposeAsync()
    {
        if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0)
            return;

        await FlushAsync().ConfigureAwait(false);

        lock (_queueLock)
        {
            _delegationQueue.Clear();
        }

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