using EonaCat.LogStack.Analytics;
using EonaCat.LogStack.Boosters;
using EonaCat.LogStack.Core;
using EonaCat.LogStack.Flows;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Channels;
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 int _disposed;
        private long _totalLoggedCount;
        private long _totalDroppedCount;
        private long _totalExceptionsCount;
        private long _totalByteCount;
        private readonly Stopwatch _startTime = Stopwatch.StartNew();

        // Per-level counters
        private long _traceCount;
        private long _debugCount;
        private long _informationCount;
        private long _warningCount;
        private long _errorCount;
        private long _criticalCount;

        // Metrics tracking
        private readonly LoggingMetrics _metrics = new LoggingMetrics();
        private readonly ConcurrentDictionary<string, PerFlowStatistics> _flowStats = new();

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

        private readonly object _modifiersLock = new object();

        // Channel-based async pipeline
        private Channel<LogEvent>? _asyncChannel;
        private Task? _asyncConsumer;
        private CancellationTokenSource? _asyncCts;

        // Dynamic level controller
        private volatile DynamicLevelController? _dynamicLevel;

        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;

            // Enable async pipeline by default
            UseAsyncPipeline();
        }

        /// <summary>
        /// Enables a Channel-based async dispatch pipeline for zero-blocking logging.
        /// Events are enqueued and consumed by a dedicated background Task.
        /// </summary>
        /// <param name="capacity">Bounded channel capacity (0 = unbounded).</param>
        public EonaCatLogStack UseAsyncPipeline(int capacity = 65536)
        {
            if (_asyncChannel != null)
            {
                return this;
            }

            _asyncCts = new CancellationTokenSource();
            _asyncChannel = capacity > 0
                ? Channel.CreateBounded<LogEvent>(new BoundedChannelOptions(capacity)
                    {
                        FullMode = BoundedChannelFullMode.DropOldest,
                        SingleReader = true,
                        AllowSynchronousContinuations = false
                    })
                : Channel.CreateUnbounded<LogEvent>(new UnboundedChannelOptions
                    {
                        SingleReader = true,
                        AllowSynchronousContinuations = false
                    });

            _asyncConsumer = Task.Run(() => ConsumeChannelAsync(_asyncCts.Token));
            return this;
        }

        /// <summary>
        /// Attaches a <see cref="DynamicLevelController"/> so the minimum level
        /// can be changed at runtime without restarting the application.
        /// </summary>
        public EonaCatLogStack UseDynamicLevel(DynamicLevelController controller)
        {
            _dynamicLevel = controller ?? throw new ArgumentNullException(nameof(controller));
            return this;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private LogLevel EffectiveMinLevel() =>
            _dynamicLevel != null ? _dynamicLevel.CurrentLevel : _minimumLevel;

        /// <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); }
            lock (_concurrentFlows)
            {
                var keep = _concurrentFlows.Where(f => f.Name != name).ToArray();
                while (_concurrentFlows.TryTake(out _)) { }
                foreach (var f in keep)
                {
                    _concurrentFlows.Add(f);
                }
            }
            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;
        }

        private void RaiseOnLog(LogMessage message)
        {
            var handlers = OnLog;

            if (handlers == null)
            {
                return;
            }

            foreach (EventHandler<LogMessage> handler in handlers.GetInvocationList())
            {
                try
                {
                    handler(this, message);
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine(ex);
                }
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Log(string message, LogLevel level = LogLevel.Information)
        {
            if (Volatile.Read(ref _disposed) == 1 || level < EffectiveMinLevel())
            {
                return;
            }

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

            ProcessLogEvent(ref builder);
        }

        /// <summary>
        /// Logs using a structured message template, binding named holes to the provided args.
        /// E.g. <c>LogTemplate(LogLevel.Information, "User {UserId} logged in from {Ip}", userId, ip)</c>
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void LogTemplate(LogLevel level, string template, params object?[] args)
        {
            if (Volatile.Read(ref _disposed) == 1 || level < EffectiveMinLevel())
            {
                return;
            }

            var parsed = MessageTemplate.FromCache(template);
            var builder = new LogEventBuilder()
                .WithLevel(level)
                .WithCategory(_category)
                .WithTimestamp(GetTimestamp());

            var rendered = parsed.RenderInto(args, builder);
            builder.WithMessage(rendered);

            ProcessLogEvent(ref builder);
        }

        /// <summary>
        /// Logs using a structured message template with an exception.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void LogTemplate(LogLevel level, Exception exception, string template, params object?[] args)
        {
            if (Volatile.Read(ref _disposed) == 1 || level < EffectiveMinLevel())
            {
                return;
            }

            var parsed = MessageTemplate.FromCache(template);
            var builder = new LogEventBuilder()
                .WithLevel(level)
                .WithCategory(_category)
                .WithException(exception)
                .WithTimestamp(GetTimestamp());

            var rendered = parsed.RenderInto(args, builder);
            builder.WithMessage(rendered);

            ProcessLogEvent(ref builder);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Log(LogLevel level, Exception exception, string message)
        {
            if (Volatile.Read(ref _disposed) == 1 || level < EffectiveMinLevel())
            {
                return;
            }

            TrackLevel(level);
            if (exception != null)
            {
                Interlocked.Increment(ref _totalExceptionsCount);
            }

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

            ProcessLogEvent(ref builder);

            RaiseOnLog(new LogMessage
            {
                Level = level,
                Exception = exception,
                Message = message,
                Category = _category,
                Origin = null
            });
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void TrackLevel(LogLevel level)
        {
            switch (level)
            {
                case LogLevel.Trace:
                    Interlocked.Increment(ref _traceCount);
                    break;
                case LogLevel.Debug:
                    Interlocked.Increment(ref _debugCount);
                    break;
                case LogLevel.Information:
                    Interlocked.Increment(ref _informationCount);
                    break;
                case LogLevel.Warning:
                    Interlocked.Increment(ref _warningCount);
                    break;
                case LogLevel.Error:
                    Interlocked.Increment(ref _errorCount);
                    break;
                case LogLevel.Critical:
                    Interlocked.Increment(ref _criticalCount);
                    break;
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Log(LogLevel level, string message, params (string Key, object Value)[] properties)
        {
            if (Volatile.Read(ref _disposed) == 1 || level < EffectiveMinLevel())
            {
                return;
            }

            TrackLevel(level);
            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);
        }

        private void Write(LogLevel level, string template, params object[] args)
        {
            Log(level, string.Format(template, args));
        }

        private void Write(LogLevel level, Exception ex, string template, params object[] args)
        {
            Log(level, ex, string.Format(template, args));
        }

        public void Trace(string template, params object[] args) => Write(LogLevel.Trace, template, args);

        public void Debug(string template, params object[] args) => Write(LogLevel.Debug, template, args);

        public void Information(string template, params object[] args) => Write(LogLevel.Information, template, args);

        public void Warning(string template, params object[] args) => Write(LogLevel.Warning, template, args);

        public void Warning(Exception ex, string template, params object[] args) => Write(LogLevel.Warning, ex, template, args);

        public void Error(string template, params object[] args) => Write(LogLevel.Error, template, args);

        public void Error(Exception ex, string template, params object[] args) => Write(LogLevel.Error, ex, template, args);

        public void Critical(string template, params object[] args) => Write(LogLevel.Critical, template, args);

        public void Critical(Exception ex, string template, params object[] args) => Write(LogLevel.Critical, ex, template, args);

        public void LogTrace(string template, params object[] args) => Write(LogLevel.Trace, template, args);

        public void LogDebug(string template, params object[] args) => Write(LogLevel.Debug, template, args);

        public void LogInformation(string template, params object[] args) => Write(LogLevel.Information, template, args);

        public void LogWarning(string template, params object[] args) => Write(LogLevel.Warning, template, args);

        public void LogWarning(Exception ex, string template, params object[] args) => Write(LogLevel.Warning, ex, template, args);

        public void LogError(string template, params object[] args) => Write(LogLevel.Error, template, args);

        public void LogError(Exception ex, string template, params object[] args) => Write(LogLevel.Error, ex, template, args);

        public void LogCritical(string template, params object[] args) => Write(LogLevel.Critical, template, args);

        public void LogCritical(Exception ex, string template, params object[] args) => Write(LogLevel.Critical, ex, template, args);

        [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
            ActionRef<LogEventBuilder>[] modifierSnapshot;
            lock (_modifiersLock) { modifierSnapshot = _modifiers.ToArray(); }

            foreach (var mod in modifierSnapshot)
            {
                try
                {
                    mod(ref builder);
                }
                catch { }
            }

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

            if (!_asyncChannel!.Writer.TryWrite(logEvent))
            {
                Interlocked.Increment(ref _totalDroppedCount);
            }
        }

        private async Task ConsumeChannelAsync(CancellationToken cancellationToken)
        {
            var reader = _asyncChannel!.Reader;
            try
            {
                await foreach (var logEvent in reader.ReadAllAsync(cancellationToken).ConfigureAwait(false))
                {
                    var flows = _concurrentFlows;
                    foreach (var flow in flows)
                    {
                        try
                        {
                            var result = await flow.BlastAsync(logEvent, cancellationToken).ConfigureAwait(false);
                            if (result == WriteResult.Dropped)
                            {
                                Interlocked.Increment(ref _totalDroppedCount);
                            }
                        }
                        catch { }
                    }
                }
            }
            catch (OperationCanceledException) { }
        }

        [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();

            int flowCount;
            int boosterCount;

            lock (_flows)
            {
                flowCount = _flows.Count;
            }

            lock (_boosters)
            {
                boosterCount = _boosters.Count;
            }

            return new LoggerDiagnostics
            {
                Category = _category,
                MinimumLevel = _minimumLevel,
                TotalLogged = Interlocked.Read(ref _totalLoggedCount),
                TotalDropped = Interlocked.Read(ref _totalDroppedCount),
                TotalExceptions = Interlocked.Read(ref _totalExceptionsCount),
                FlowCount = flowCount,
                BoosterCount = boosterCount,
                Flows = flowDiagnostics
            };
        }

        /// <summary>
        /// Gets detailed metrics about the logger
        /// </summary>
        public LoggerMetrics GetMetrics()
        {
            var elapsedMs = _startTime.ElapsedMilliseconds;
            var writesPerSecond = elapsedMs > 0 ? (_totalLoggedCount * 1000.0) / elapsedMs : 0;

            return new LoggerMetrics
            {
                TotalLogged = Interlocked.Read(ref _totalLoggedCount),
                TotalDropped = Interlocked.Read(ref _totalDroppedCount),
                TotalExceptions = Interlocked.Read(ref _totalExceptionsCount),
                TotalBytes = Interlocked.Read(ref _totalByteCount),
                WritesPerSecond = writesPerSecond,
                TraceCount = Interlocked.Read(ref _traceCount),
                DebugCount = Interlocked.Read(ref _debugCount),
                InformationCount = Interlocked.Read(ref _informationCount),
                WarningCount = Interlocked.Read(ref _warningCount),
                ErrorCount = Interlocked.Read(ref _errorCount),
                CriticalCount = Interlocked.Read(ref _criticalCount),
                UptimeMilliseconds = elapsedMs,
                FlowMetrics = _flowStats.Values.Select(s => s.GetSnapshot()).ToList()
            };
        }

        /// <summary>
        /// Gets the metrics collector for analytics
        /// </summary>
        public LoggingMetrics GetMetricsCollector() => _metrics;


        public async ValueTask DisposeAsync()
        {
            if (Interlocked.Exchange(ref _disposed, 1) == 1)
            {
                return;
            }

            // Drain and stop the async channel pipeline if active
            if (_asyncChannel != null)
            {
                _asyncChannel.Writer.TryComplete();

                if (_asyncConsumer != null)
                {
                    try
                    {
                        await _asyncConsumer.ConfigureAwait(false);
                    }
                    catch (Exception ex)
                    {
                        System.Diagnostics.Debug.WriteLine(ex);
                    }
                }
            }

            await FlushAsync().ConfigureAwait(false);

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

            _asyncCts?.Dispose();
            GC.SuppressFinalize(this);
        }
    }
}
