using EonaCat.LogStack.Boosters;
using EonaCat.LogStack.Core;
using EonaCat.LogStack.Flows;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
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
{
    /// <summary>
    /// EonaCat logger with flow-based architecture, booster, and pre-build modifier hook.
    /// Designed for zero-allocation logging paths and superior memory efficiency.
    /// </summary>
    public sealed class EonaCatLogStack : IAsyncDisposable
    {
        private readonly string _category;
        private readonly List<IFlow> _flows = new List<IFlow>();
        private readonly List<IBooster> _boosters = new List<IBooster>();
        private readonly ConcurrentBag<IFlow> _concurrentFlows = new ConcurrentBag<IFlow>();
        private readonly LogLevel _minimumLevel;
        private readonly TimestampMode _timestampMode;

        private volatile bool _isDisposed;
        private long _totalLoggedCount;
        private long _totalDroppedCount;

        private readonly List<ActionRef<LogEventBuilder>> _modifiers = new List<ActionRef<LogEventBuilder>>();
        public delegate void ActionRef<T>(ref T item);

        private readonly object _modifiersLock = new object();

        public event EventHandler<LogMessage> OnLog;

        /// <summary>
        /// Creates a new logger instance
        /// </summary>
        public EonaCatLogStack(string category = "Application",
            LogLevel minimumLevel = LogLevel.Trace,
            TimestampMode timestampMode = TimestampMode.Utc)
        {
            _category = category ?? throw new ArgumentNullException(nameof(category));
            _minimumLevel = minimumLevel;
            _timestampMode = timestampMode;
        }

        /// <summary>
        /// Adds a flow (output destination) to this logger
        /// </summary>
        public EonaCatLogStack AddFlow(IFlow flow)
        {
            if (flow == null)
            {
                throw new ArgumentNullException(nameof(flow));
            }

            lock (_flows) { _flows.Add(flow); }
            _concurrentFlows.Add(flow);
            return this;
        }

        /// <summary>
        /// Adds a booster to this logger
        /// </summary>
        public EonaCatLogStack AddBooster(IBooster booster)
        {
            if (booster == null)
            {
                throw new ArgumentNullException(nameof(booster));
            }

            lock (_boosters) { _boosters.Add(booster); }
            return this;
        }

        /// <summary>
        /// Removes a flow by name
        /// </summary>
        public EonaCatLogStack RemoveFlow(string name)
        {
            lock (_flows) { _flows.RemoveAll(f => f.Name == name); }
            return this;
        }

        /// <summary>
        /// Removes a booster by name
        /// </summary>
        public EonaCatLogStack RemoveBooster(string name)
        {
            lock (_boosters) { _boosters.RemoveAll(b => b.Name == name); }
            return this;
        }

        /// <summary>
        /// Adds a modifier to run before building the LogEvent.
        /// Return false to cancel logging.
        /// </summary>
        public EonaCatLogStack AddModifier(ActionRef<LogEventBuilder> modifier)
        {
            if (modifier == null)
            {
                throw new ArgumentNullException(nameof(modifier));
            }

            lock (_modifiersLock) { _modifiers.Add(modifier); }
            return this;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Log(string message, LogLevel level = LogLevel.Information)
        {
            if (_isDisposed || level < _minimumLevel)
            {
                return;
            }

            var builder = new LogEventBuilder()
                .WithLevel(level)
                .WithCategory(_category)
                .WithMessage(message)
                .WithTimestamp(GetTimestamp());

            ProcessLogEvent(ref builder);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Log(LogLevel level, Exception exception, string message)
        {
            if (_isDisposed || level < _minimumLevel)
            {
                return;
            }

            var builder = new LogEventBuilder()
                .WithLevel(level)
                .WithCategory(_category)
                .WithMessage(message)
                .WithException(exception)
                .WithTimestamp(GetTimestamp());

            ProcessLogEvent(ref builder);

            OnLog?.Invoke(this, new LogMessage
            {
                Level = level,
                Exception = exception,
                Message = message,
                Category = _category,
                Origin = null
            });
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Log(LogLevel level, string message, params (string Key, object Value)[] properties)
        {
            if (_isDisposed || level < _minimumLevel)
            {
                return;
            }

            var builder = new LogEventBuilder()
                .WithLevel(level)
                .WithCategory(_category)
                .WithMessage(message)
                .WithTimestamp(GetTimestamp());

            foreach (var (key, value) in properties)
            {
                builder.WithProperty(key, value);
            }

            ProcessLogEvent(ref builder);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Trace(string message) => Log(LogLevel.Trace, message);
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Debug(string message) => Log(LogLevel.Debug, message);
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Information(string message) => Log(LogLevel.Information, message);
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Warning(string message) => Log(LogLevel.Warning, message);
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Warning(Exception ex, string message) => Log(LogLevel.Warning, ex, message);
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Error(string message) => Log(LogLevel.Error, message);
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Error(Exception ex, string message) => Log(LogLevel.Error, ex, message);
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Critical(string message) => Log(LogLevel.Critical, message);
        [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Critical(Exception ex, string message) => Log(LogLevel.Critical, ex, message);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void ProcessLogEvent(ref LogEventBuilder builder)
        {
            // Apply boosters
            lock (_boosters)
            {
                foreach (var booster in _boosters)
                {
                    try
                    {
                        if (!booster.Boost(ref builder))
                        {
                            return; // filtered out
                        }
                    }
                    catch { }
                }
            }

            // Apply modifiers
            foreach (var mod in _modifiers)
            {
                try
                {
                    mod(ref builder);
                }
                catch { }
            }

            var logEvent = builder.Build();
            Interlocked.Increment(ref _totalLoggedCount);

            // Blast to flows
            foreach (var flow in _concurrentFlows)
            {
                try
                {
                    var result = flow.BlastAsync(logEvent).GetAwaiter().GetResult();
                    if (result == WriteResult.Dropped)
                    {
                        Interlocked.Increment(ref _totalDroppedCount);
                    }
                }
                catch { }
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private long GetTimestamp()
        {
            switch (_timestampMode)
            {
                case TimestampMode.Local: return DateTime.Now.Ticks;
                case TimestampMode.HighPrecision: return System.Diagnostics.Stopwatch.GetTimestamp();
                default: return DateTime.UtcNow.Ticks;
            }
        }

        public async Task FlushAsync(CancellationToken cancellationToken = default)
        {
            var tasks = _concurrentFlows.Select(f => f.FlushAsync(cancellationToken));
            await Task.WhenAll(tasks).ConfigureAwait(false);
        }

        public LoggerDiagnostics GetDiagnostics()
        {
            var flowDiagnostics = _concurrentFlows
                .Select(f => f is FlowBase fb ? fb.GetDiagnostics() : null)
                .Where(d => d != null)
                .ToList();

            return new LoggerDiagnostics
            {
                Category = _category,
                MinimumLevel = _minimumLevel,
                TotalLogged = Interlocked.Read(ref _totalLoggedCount),
                TotalDropped = Interlocked.Read(ref _totalDroppedCount),
                FlowCount = _flows.Count,
                BoosterCount = _boosters.Count,
                Flows = flowDiagnostics
            };
        }

        public async ValueTask DisposeAsync()
        {
            if (_isDisposed)
            {
                return;
            }

            _isDisposed = true;

            await FlushAsync().ConfigureAwait(false);

            var disposeTasks = _concurrentFlows.Select(f => f.DisposeAsync().AsTask());
            await Task.WhenAll(disposeTasks).ConfigureAwait(false);

            GC.SuppressFinalize(this);
        }
    }
}
