using EonaCat.LogStack.Core;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
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>
    /// CorrelatedEventFlow tracks and correlates related events across flows and time windows.
    /// Enables request tracing, transaction tracking, and detection of related log patterns.
    /// </summary>
    public sealed class CorrelatedEventFlow : FlowBase
    {
        private readonly int _maxCorrelationGroups;
        private readonly TimeSpan _correlationWindow;
        private readonly ConcurrentDictionary<string, CorrelationGroup> _correlationGroups = new();
        private readonly object _cleanupLock = new();

        /// <summary>
        /// Creates a new CorrelatedEventFlow
        /// </summary>
        /// <param name="maxCorrelationGroups">Maximum number of correlation groups to track (default 10000)</param>
        /// <param name="correlationWindow">Time window for correlating events (default 5 minutes)</param>
        /// <param name="minimumLevel">Minimum log level to process</param>
        public CorrelatedEventFlow(
            int maxCorrelationGroups = 10000,
            TimeSpan? correlationWindow = null,
            LogLevel minimumLevel = LogLevel.Trace)
            : base("CorrelatedEvent", minimumLevel)
        {
            _maxCorrelationGroups = Math.Max(1000, maxCorrelationGroups);
            _correlationWindow = correlationWindow ?? TimeSpan.FromMinutes(5);
        }

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

            try
            {
                var correlationId = ExtractCorrelationId(logEvent);
                if (string.IsNullOrEmpty(correlationId))
                    return WriteResult.Success;

                // Get or create correlation group
                var group = _correlationGroups.AddOrUpdate(
                    correlationId,
                    new CorrelationGroup(correlationId),
                    (key, existing) => existing
                );

                group.AddEvent(logEvent);

                // Cleanup if needed
                if (_correlationGroups.Count > _maxCorrelationGroups)
                {
                    CleanupExpiredGroups();
                }

                return await Task.FromResult(WriteResult.Success);
            }
            catch (Exception ex)
            {
                return WriteResult.Failed;
            }
        }

        public override async Task<WriteResult> BlastBatchAsync(ReadOnlyMemory<LogEvent> logEvents, CancellationToken cancellationToken = default)
        {
            try
            {
                foreach (var logEvent in logEvents.Span)
                {
                    if (IsLogLevelEnabled(logEvent))
                    {
                        var correlationId = ExtractCorrelationId(logEvent);
                        if (!string.IsNullOrEmpty(correlationId))
                        {
                            var group = _correlationGroups.AddOrUpdate(
                                correlationId,
                                new CorrelationGroup(correlationId),
                                (key, existing) => existing
                            );

                            group.AddEvent(logEvent);
                        }
                    }
                }

                if (_correlationGroups.Count > _maxCorrelationGroups)
                {
                    CleanupExpiredGroups();
                }

                return await Task.FromResult(WriteResult.Success);
            }
            catch (Exception ex)
            {
                return WriteResult.Failed;
            }
        }

        public override async Task FlushAsync(CancellationToken cancellationToken = default)
        {
            CleanupExpiredGroups();
            await Task.CompletedTask;
        }

        /// <summary>
        /// Get all events for a correlation ID
        /// </summary>
        public IEnumerable<LogEvent> GetCorrelatedEvents(string correlationId)
        {
            if (_correlationGroups.TryGetValue(correlationId, out var group))
                return group.Events.ToList();

            return new List<LogEvent>();
        }

        /// <summary>
        /// Get correlation statistics
        /// </summary>
        public CorrelationStatistics GetStatistics()
        {
            var stats = new CorrelationStatistics
            {
                TotalCorrelationIds = _correlationGroups.Count,
                TotalTrackedEvents = _correlationGroups.Values.Sum(g => g.Events.Count),
                AverageEventsPerCorrelation = _correlationGroups.Count > 0 
                    ? _correlationGroups.Values.Average(g => g.Events.Count) 
                    : 0
            };

            // Calculate event distribution
            if (_correlationGroups.Count > 0)
            {
                var eventCounts = _correlationGroups.Values.Select(g => g.Events.Count).ToList();
                stats.MinEventsPerCorrelation = eventCounts.Min();
                stats.MaxEventsPerCorrelation = eventCounts.Max();
                stats.MedianEventsPerCorrelation = GetMedian(eventCounts);
            }

            return stats;
        }

        /// <summary>
        /// Find correlation groups with errors
        /// </summary>
        public IEnumerable<CorrelationGroup> GetErrorCorrelations()
        {
            return _correlationGroups.Values
                .Where(g => g.Events.Any(e => e.Level >= LogLevel.Error))
                .ToList();
        }

        /// <summary>
        /// Find slow correlation groups (based on duration property)
        /// </summary>
        public IEnumerable<CorrelationGroup> GetSlowCorrelations(TimeSpan minimumDuration)
        {
            return _correlationGroups.Values
                .Where(g => g.Time().TotalMilliseconds >= minimumDuration.TotalMilliseconds)
                .ToList();
        }

        /// <summary>
        /// Get correlation groups by category pattern
        /// </summary>
        public IEnumerable<CorrelationGroup> QueryByPattern(string pattern, StringComparison comparison = StringComparison.OrdinalIgnoreCase)
        {
            return _correlationGroups.Values
                .Where(g => g.Events.Any(e => 
                {
                    var msg = e.Message.ToString();
                    return msg.Contains(pattern, comparison);
                }))
                .ToList();
        }

        /// <summary>
        /// Clear all correlation tracking
        /// </summary>
        public void Clear()
        {
            _correlationGroups.Clear();
        }

        // Private helpers

        private string ExtractCorrelationId(LogEvent logEvent)
        {
            // Try to extract from common sources
            if (logEvent.Properties != null)
            {
                if (logEvent.Properties.TryGetValue("CorrelationId", out var corrId) && corrId is string str)
                    return str;
                if (logEvent.Properties.TryGetValue("RequestId", out var reqId) && reqId is string str2)
                    return str2;
                if (logEvent.Properties.TryGetValue("TraceId", out var traceId) && traceId is string str3)
                    return str3;
            }

            // Try thread-based correlation
            return null;
        }

        private void CleanupExpiredGroups()
        {
            lock (_cleanupLock)
            {
                var now = DateTime.UtcNow;
                var expiredIds = _correlationGroups
                    .Where(kvp => now - kvp.Value.LastEventTime > _correlationWindow)
                    .Select(kvp => kvp.Key)
                    .ToList();

                foreach (var id in expiredIds)
                {
                    _correlationGroups.TryRemove(id, out _);
                }
            }
        }

        private double GetMedian(List<int> values)
        {
            if (values.Count == 0) return 0;
            var sorted = values.OrderBy(x => x).ToList();
            if (sorted.Count % 2 == 0)
                return (sorted[sorted.Count / 2 - 1] + sorted[sorted.Count / 2]) / 2.0;
            return sorted[sorted.Count / 2];
        }

        public override async ValueTask DisposeAsync()
        {
            Clear();
            await base.DisposeAsync();
        }
    }

    /// <summary>
    /// Represents a group of correlated log events
    /// </summary>
    public class CorrelationGroup
    {
        private readonly List<LogEvent> _events = new();
        private readonly object _eventLock = new();

        public string CorrelationId { get; }
        public IReadOnlyList<LogEvent> Events
        {
            get
            {
                lock (_eventLock)
                    return _events.AsReadOnly();
            }
        }
        public DateTime FirstEventTime { get; private set; }
        public DateTime LastEventTime { get; private set; }

        public CorrelationGroup(string correlationId)
        {
            CorrelationId = correlationId ?? throw new ArgumentNullException(nameof(correlationId));
            FirstEventTime = DateTime.UtcNow;
            LastEventTime = DateTime.UtcNow;
        }

        public void AddEvent(LogEvent logEvent)
        {
            lock (_eventLock)
            {
                _events.Add(logEvent);
                LastEventTime = DateTime.UtcNow;
            }
        }

        /// <summary>
        /// Get the total time span of this correlation group
        /// </summary>
        public TimeSpan Time()
        {
            lock (_eventLock)
            {
                if (_events.Count == 0) return TimeSpan.Zero;
                return new TimeSpan(_events[_events.Count - 1].Timestamp - _events[0].Timestamp);
            }
        }

        /// <summary>
        /// Get events at specific log level
        /// </summary>
        public IEnumerable<LogEvent> GetEventsByLevel(LogLevel level)
        {
            lock (_eventLock)
                return _events.Where(e => e.Level == level).ToList();
        }

        /// <summary>
        /// Check if this group contains any exceptions
        /// </summary>
        public bool HasExceptions
        {
            get
            {
                lock (_eventLock)
                    return _events.Any(e => e.Exception != null);
            }
        }

        /// <summary>
        /// Get event count by category
        /// </summary>
        public Dictionary<string, int> GetEventsByCategory()
        {
            lock (_eventLock)
            {
                return _events
                    .GroupBy(e => e.Category ?? "Unknown")
                    .ToDictionary(g => g.Key, g => g.Count());
            }
        }

        public override string ToString()
        {
            lock (_eventLock)
            {
                var errorCount = _events.Count(e => e.Level >= LogLevel.Error);
                var duration = Time();
                return $"CorrelationGroup [ID: {CorrelationId}, Events: {_events.Count}, Errors: {errorCount}, Duration: {duration.TotalMilliseconds:F0}ms]";
            }
        }
    }

    /// <summary>
    /// Statistics about correlation tracking
    /// </summary>
    public class CorrelationStatistics
    {
        public int TotalCorrelationIds { get; set; }
        public int TotalTrackedEvents { get; set; }
        public double AverageEventsPerCorrelation { get; set; }
        public int MinEventsPerCorrelation { get; set; }
        public int MaxEventsPerCorrelation { get; set; }
        public double MedianEventsPerCorrelation { get; set; }

        public override string ToString()
        {
            return $"CorrelationStats: IDs={TotalCorrelationIds}, Events={TotalTrackedEvents}, " +
                   $"Avg={AverageEventsPerCorrelation:F1}, Min={MinEventsPerCorrelation}, Max={MaxEventsPerCorrelation}";
        }
    }
}
