using EonaCat.LogStack.Core;
using System;
using System.Runtime.CompilerServices;
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 decorator flow that forwards events to an inner flow only when a user-supplied
/// predicate returns <c>true</c>.
///
/// Common use-cases:
///   • Category-based routing  - route only "Database" category events to file
///   • Level-range filtering   - forward Warning≤level&lt;Error to one flow
///   • Property filtering      - only forward events that carry a specific property
///   • Exception routing       - send events with SqlException to a special alert flow
///
/// <example>
/// <code>
/// // Route only Database category errors to a dedicated file
/// new LogBuilder()
///     .WriteToConditional(
///         predicate: e => e.Category == "Database" &amp;&amp; e.Level >= LogLevel.Error,
///         inner: new FileFlow("logs/db-errors"))
/// </code>
/// </example>
/// </summary>
public sealed class ConditionalFlow : FlowBase
{
    private readonly IFlow _inner;
    private readonly Func<LogEvent, bool> _predicate;

    public ConditionalFlow(
        IFlow inner,
        Func<LogEvent, bool> predicate,
        LogLevel minimumLevel = LogLevel.Trace)
        : base($"Conditional({inner?.Name ?? "?"})", minimumLevel)
    {
        _inner     = inner     ?? throw new ArgumentNullException(nameof(inner));
        _predicate = predicate ?? throw new ArgumentNullException(nameof(predicate));
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public override async Task<WriteResult> BlastAsync(LogEvent logEvent, CancellationToken cancellationToken = default)
    {
        if (!IsEnabled || !IsLogLevelEnabled(logEvent))
        {
            return WriteResult.LevelFiltered;
        }

        bool matches;
        try { matches = _predicate(logEvent); }
        catch { matches = false; }

        if (!matches)
        {
            Interlocked.Increment(ref DroppedCount);
            return WriteResult.Dropped;
        }

        var result = await _inner.BlastAsync(logEvent, cancellationToken).ConfigureAwait(false);
        Interlocked.Increment(ref BlastedCount);
        return result;
    }

    public override async Task<WriteResult> BlastBatchAsync(
        ReadOnlyMemory<LogEvent> logEvents, CancellationToken cancellationToken = default)
    {
        if (!IsEnabled)
        {
            return WriteResult.FlowDisabled;
        }

        var result = WriteResult.Success;
        var eventsArray = logEvents.ToArray();
        foreach (var ev in eventsArray)
        {
            bool matches;
            try { matches = ev.Level >= MinimumLevel && _predicate(ev); }
            catch { matches = false; }

            if (!matches) { Interlocked.Increment(ref DroppedCount); continue; }

            var r = await _inner.BlastAsync(ev, cancellationToken).ConfigureAwait(false);
            Interlocked.Increment(ref BlastedCount);
            if (r != WriteResult.Success)
            {
                result = r;
            }
        }

        return result;
    }

    public override Task FlushAsync(CancellationToken cancellationToken = default)
        => _inner.FlushAsync(cancellationToken);

    public override ValueTask DisposeAsync() => _inner.DisposeAsync();
}
