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 filters log events based on custom predicates before passing to an inner flow.
    /// Useful for applying complex filtering logic beyond simple log level filtering.
    /// </summary>
    public sealed class AdvancedFilterFlow : FlowBase
    {
        private readonly IFlow _innerFlow;
        private readonly List<Predicate<LogEvent>> _filters;

        public AdvancedFilterFlow(
            IFlow innerFlow,
            LogLevel minimumLevel = LogLevel.Trace)
            : base(innerFlow?.Name + "_Filter" ?? "AdvancedFilterFlow", minimumLevel)
        {
            _innerFlow = innerFlow ?? throw new ArgumentNullException(nameof(innerFlow));
            _filters = new List<Predicate<LogEvent>>();
        }

        /// <summary>
        /// Adds a filter predicate. All predicates must return true for the log event to pass through.
        /// </summary>
        public void AddFilter(Predicate<LogEvent> filter)
        {
            if (filter == null)
            {
                throw new ArgumentNullException(nameof(filter));
            }

            lock (_filters)
            {
                _filters.Add(filter);
            }
        }

        /// <summary>
        /// Adds a filter that matches messages containing specific text (case-insensitive).
        /// </summary>
        public void AddMessageContainsFilter(string text)
        {
            if (string.IsNullOrEmpty(text))
            {
                throw new ArgumentNullException(nameof(text));
            }

            var lowerText = text.ToLowerInvariant();
            AddFilter(logEvent => logEvent.Message.ToString().ToLowerInvariant().Contains(lowerText));
        }

        /// <summary>
        /// Adds a filter that matches specific log categories.
        /// </summary>
        public void AddCategoryFilter(params string[] categories)
        {
            if (categories == null || categories.Length == 0)
            {
                throw new ArgumentException("At least one category must be specified", nameof(categories));
            }

            var categorySet = new HashSet<string>(categories, StringComparer.OrdinalIgnoreCase);
            AddFilter(logEvent => categorySet.Contains(logEvent.Category));
        }

        /// <summary>
        /// Adds a filter that excludes specific log categories.
        /// </summary>
        public void AddExcludeCategoryFilter(params string[] categories)
        {
            if (categories == null || categories.Length == 0)
            {
                throw new ArgumentException("At least one category must be specified", nameof(categories));
            }

            var categorySet = new HashSet<string>(categories, StringComparer.OrdinalIgnoreCase);
            AddFilter(logEvent => !categorySet.Contains(logEvent.Category));
        }

        /// <summary>
        /// Adds a filter for exceptions.
        /// </summary>
        public void AddExceptionFilter(bool onlyWithExceptions = true)
        {
            AddFilter(logEvent => onlyWithExceptions ? logEvent.Exception != null : logEvent.Exception == null);
        }

        private bool PassesAllFilters(LogEvent logEvent)
        {
            lock (_filters)
            {
                foreach (var filter in _filters)
                {
                    if (!filter(logEvent))
                    {
                        return false;
                    }
                }
            }

            return true;
        }

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

            if (!PassesAllFilters(logEvent))
            {
                Interlocked.Increment(ref DroppedCount);
                return WriteResult.Success;
            }

            var result = await _innerFlow.BlastAsync(logEvent, cancellationToken).ConfigureAwait(false);
            if (result == WriteResult.Success)
            {
                Interlocked.Increment(ref BlastedCount);
            }
            else
            {
                Interlocked.Increment(ref DroppedCount);
            }

            return result;
        }

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

            var filteredEvents = new List<LogEvent>();
            foreach (var logEvent in logEvents.Span)
            {
                if (IsLogLevelEnabled(logEvent) && PassesAllFilters(logEvent))
                {
                    filteredEvents.Add(logEvent);
                    Interlocked.Increment(ref BlastedCount);
                }
                else
                {
                    Interlocked.Increment(ref DroppedCount);
                }
            }

            if (filteredEvents.Count > 0)
            {
                return await _innerFlow.BlastBatchAsync(filteredEvents.ToArray(), cancellationToken).ConfigureAwait(false);
            }

            return WriteResult.Success;
        }

        public override async Task FlushAsync(CancellationToken cancellationToken = default)
        {
            await _innerFlow.FlushAsync(cancellationToken).ConfigureAwait(false);
        }

        public override async ValueTask DisposeAsync()
        {
            await _innerFlow.DisposeAsync().ConfigureAwait(false);
            await base.DisposeAsync().ConfigureAwait(false);
        }
    }
}
