using EonaCat.LogStack.Core;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
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 collects performance metrics about log processing.
    /// Tracks throughput, latency, and other performance indicators.
    /// </summary>
    public sealed class MetricsAggregatorFlow : FlowBase
    {
        private readonly object _metricsLock = new();
        private long _totalEventsProcessed;
        private long _totalEventsDropped;
        private long _totalLatencyMs;
        private long _minLatencyMs = long.MaxValue;
        private long _maxLatencyMs = 0;
        private DateTime _metricsStartTime;
        private readonly Queue<long> _recentLatencies;
        private readonly int _maxRecentLatencies;

        public MetricsAggregatorFlow(
            int maxRecentLatencies = 100,
            LogLevel minimumLevel = LogLevel.Trace)
            : base("MetricsAggregatorFlow", minimumLevel)
        {
            _maxRecentLatencies = maxRecentLatencies > 0 ? maxRecentLatencies : 100;
            _recentLatencies = new Queue<long>(_maxRecentLatencies);
            _metricsStartTime = DateTime.UtcNow;
        }

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

            var sw = Stopwatch.StartNew();
            try
            {
                sw.Stop();
                RecordMetric(success: true, latencyMs: sw.ElapsedMilliseconds);
                Interlocked.Increment(ref BlastedCount);
                return WriteResult.Success;
            }
            catch (Exception)
            {
                sw.Stop();
                RecordMetric(success: false, latencyMs: sw.ElapsedMilliseconds);
                Interlocked.Increment(ref DroppedCount);
                return WriteResult.Failed;
            }
        }

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

            var sw = Stopwatch.StartNew();
            try
            {
                int processedCount = 0;
                foreach (var logEvent in logEvents.Span)
                {
                    if (IsLogLevelEnabled(logEvent))
                    {
                        processedCount++;
                    }
                }

                sw.Stop();
                RecordMetric(success: true, latencyMs: sw.ElapsedMilliseconds);
                Interlocked.Add(ref BlastedCount, processedCount);
                Interlocked.Add(ref DroppedCount, logEvents.Length - processedCount);
                return WriteResult.Success;
            }
            catch (Exception)
            {
                sw.Stop();
                RecordMetric(success: false, latencyMs: sw.ElapsedMilliseconds);
                Interlocked.Add(ref DroppedCount, logEvents.Length);
                return WriteResult.Failed;
            }
        }

        private void RecordMetric(bool success, long latencyMs)
        {
            lock (_metricsLock)
            {
                if (success)
                {
                    _totalEventsProcessed++;
                    _totalLatencyMs += latencyMs;
                    _minLatencyMs = Math.Min(_minLatencyMs, latencyMs);
                    _maxLatencyMs = Math.Max(_maxLatencyMs, latencyMs);

                    if (_recentLatencies.Count >= _maxRecentLatencies)
                    {
                        _recentLatencies.Dequeue();
                    }
                    _recentLatencies.Enqueue(latencyMs);
                }
                else
                {
                    _totalEventsDropped++;
                }
            }
        }

        /// <summary>
        /// Gets current performance metrics snapshot.
        /// </summary>
        public MetricsSnapshot GetMetricsSnapshot()
        {
            lock (_metricsLock)
            {
                var totalProcessed = _totalEventsProcessed;
                var totalDropped = _totalEventsDropped;
                var totalLatency = _totalLatencyMs;
                var minLatency = _minLatencyMs == long.MaxValue ? 0 : _minLatencyMs;
                var maxLatency = _maxLatencyMs;
                var recentLatencies = _recentLatencies.ToArray();

                var avgLatency = totalProcessed > 0 ? totalLatency / totalProcessed : 0;
                var medianLatency = recentLatencies.Length > 0 
                    ? recentLatencies[recentLatencies.Length / 2] 
                    : 0;

                var elapsed = DateTime.UtcNow - _metricsStartTime;
                var eventsPerSecond = elapsed.TotalSeconds > 0 
                    ? totalProcessed / elapsed.TotalSeconds 
                    : 0;

                return new MetricsSnapshot
                {
                    TotalProcessed = totalProcessed,
                    TotalDropped = totalDropped,
                    AverageLatencyMs = avgLatency,
                    MinLatencyMs = minLatency,
                    MaxLatencyMs = maxLatency,
                    MedianLatencyMs = medianLatency,
                    EventsPerSecond = eventsPerSecond,
                    ElapsedTime = elapsed,
                    CollectedAt = DateTime.UtcNow
                };
            }
        }

        /// <summary>
        /// Resets metrics counters.
        /// </summary>
        public void ResetMetrics()
        {
            lock (_metricsLock)
            {
                _totalEventsProcessed = 0;
                _totalEventsDropped = 0;
                _totalLatencyMs = 0;
                _minLatencyMs = long.MaxValue;
                _maxLatencyMs = 0;
                _recentLatencies.Clear();
                _metricsStartTime = DateTime.UtcNow;
            }
        }

        public override async Task FlushAsync(CancellationToken cancellationToken = default)
        {
            // Metrics flow doesn't buffer events, nothing to flush
            return;
        }

        public override ValueTask DisposeAsync()
        {
            return base.DisposeAsync();
        }
    }

    /// <summary>
    /// A snapshot of performance metrics at a point in time.
    /// </summary>
    public sealed class MetricsSnapshot
    {
        /// <summary>Total number of log events processed</summary>
        public long TotalProcessed { get; set; }

        /// <summary>Total number of log events dropped</summary>
        public long TotalDropped { get; set; }

        /// <summary>Average latency in milliseconds</summary>
        public long AverageLatencyMs { get; set; }

        /// <summary>Minimum latency in milliseconds</summary>
        public long MinLatencyMs { get; set; }

        /// <summary>Maximum latency in milliseconds</summary>
        public long MaxLatencyMs { get; set; }

        /// <summary>Median latency in milliseconds</summary>
        public long MedianLatencyMs { get; set; }

        /// <summary>Number of events processed per second</summary>
        public double EventsPerSecond { get; set; }

        /// <summary>Total elapsed time since metrics started</summary>
        public TimeSpan ElapsedTime { get; set; }

        /// <summary>When this snapshot was collected</summary>
        public DateTime CollectedAt { get; set; }

        public override string ToString()
        {
            return $"Metrics[Processed={TotalProcessed}, Dropped={TotalDropped}, " +
                   $"AvgLat={AverageLatencyMs}ms, MinLat={MinLatencyMs}ms, MaxLat={MaxLatencyMs}ms, " +
                   $"Median={MedianLatencyMs}ms, Rate={EventsPerSecond:F2} eps, Elapsed={ElapsedTime.TotalSeconds:F2}s]";
        }
    }
}
