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 chains multiple flows together, passing log events through them in sequence.
    /// Useful for composing complex logging behavior from simple components.
    /// </summary>
    public sealed class PipelineFlow : FlowBase
    {
        private readonly List<IFlow> _flows;
        private readonly StopOnError _stopOnError;

        public enum StopOnError
        {
            /// <summary>Continue to next flow even if current flow fails</summary>
            Continue = 0,

            /// <summary>Stop pipeline if any flow fails</summary>
            StopImmediate = 1,

            /// <summary>Stop pipeline if core flows fail, continue for non-critical flows</summary>
            StopOnCritical = 2
        }

        public PipelineFlow(
            LogLevel minimumLevel = LogLevel.Trace,
            StopOnError stopOnError = StopOnError.Continue)
            : base("PipelineFlow", minimumLevel)
        {
            _flows = new List<IFlow>();
            _stopOnError = stopOnError;
        }

        /// <summary>
        /// Adds a flow to the pipeline. Flows are executed in the order they are added.
        /// </summary>
        public PipelineFlow AddFlow(IFlow flow)
        {
            if (flow == null)
            {
                throw new ArgumentNullException(nameof(flow));
            }

            lock (_flows)
            {
                _flows.Add(flow);
            }

            return this;
        }

        /// <summary>
        /// Adds multiple flows to the pipeline.
        /// </summary>
        public PipelineFlow AddFlows(params IFlow[] flows)
        {
            if (flows == null)
            {
                throw new ArgumentNullException(nameof(flows));
            }

            foreach (var flow in flows)
            {
                AddFlow(flow);
            }

            return this;
        }

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

            List<IFlow> flowsToExecute;
            lock (_flows)
            {
                flowsToExecute = new List<IFlow>(_flows);
            }

            if (flowsToExecute.Count == 0)
            {
                Interlocked.Increment(ref DroppedCount);
                return WriteResult.Success;
            }

            var overallResult = WriteResult.Success;

            foreach (var flow in flowsToExecute)
            {
                try
                {
                    if (flow.IsEnabled && logEvent.Level >= flow.MinimumLevel)
                    {
                        var result = await flow.BlastAsync(logEvent, cancellationToken).ConfigureAwait(false);

                        if (result != WriteResult.Success)
                        {
                            overallResult = result;

                            if (_stopOnError == StopOnError.StopImmediate)
                            {
                                break;
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine($"PipelineFlow error in {flow.Name}: {ex.Message}");
                    overallResult = WriteResult.Failed;

                    if (_stopOnError == StopOnError.StopImmediate)
                    {
                        break;
                    }
                }
            }

            if (overallResult == WriteResult.Success)
            {
                Interlocked.Increment(ref BlastedCount);
            }
            else
            {
                Interlocked.Increment(ref DroppedCount);
            }

            return overallResult;
        }

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

            List<IFlow> flowsToExecute;
            lock (_flows)
            {
                flowsToExecute = new List<IFlow>(_flows);
            }

            if (flowsToExecute.Count == 0)
            {
                Interlocked.Add(ref DroppedCount, logEvents.Length);
                return WriteResult.Success;
            }

            var overallResult = WriteResult.Success;
            var logsProcessed = 0;

            foreach (var flow in flowsToExecute)
            {
                try
                {
                    if (flow.IsEnabled)
                    {
                        var result = await flow.BlastBatchAsync(logEvents, cancellationToken).ConfigureAwait(false);

                        if (result != WriteResult.Success)
                        {
                            overallResult = result;

                            if (_stopOnError == StopOnError.StopImmediate)
                            {
                                break;
                            }
                        }

                        logsProcessed += logEvents.Length;
                    }
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine($"PipelineFlow batch error in {flow.Name}: {ex.Message}");
                    overallResult = WriteResult.Failed;

                    if (_stopOnError == StopOnError.StopImmediate)
                    {
                        break;
                    }
                }
            }

            if (logsProcessed > 0)
            {
                Interlocked.Add(ref BlastedCount, logsProcessed);
            }
            else
            {
                Interlocked.Add(ref DroppedCount, logEvents.Length);
            }

            return overallResult;
        }

        public override async Task FlushAsync(CancellationToken cancellationToken = default)
        {
            List<IFlow> flowsToFlush;
            lock (_flows)
            {
                flowsToFlush = new List<IFlow>(_flows);
            }

            foreach (var flow in flowsToFlush)
            {
                try
                {
                    await flow.FlushAsync(cancellationToken).ConfigureAwait(false);
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine($"PipelineFlow flush error in {flow.Name}: {ex.Message}");
                }
            }
        }

        public override async ValueTask DisposeAsync()
        {
            List<IFlow> flowsToDispose;
            lock (_flows)
            {
                flowsToDispose = new List<IFlow>(_flows);
            }

            foreach (var flow in flowsToDispose)
            {
                try
                {
                    await flow.DisposeAsync().ConfigureAwait(false);
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine($"PipelineFlow dispose error in {flow.Name}: {ex.Message}");
                }
            }

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