using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;

namespace EonaCat.LogStack.Telemetry
{
    /// <summary>
    /// Aggregates telemetry data from multiple sources and flows.
    /// Correlates metrics and provides unified telemetry snapshots.
    /// </summary>
    public sealed class TelemetryAggregator
    {
        private readonly ConcurrentDictionary<string, MetricValue> _metrics = new();
        private readonly ConcurrentDictionary<string, GaugeValue> _gauges = new();
        private readonly List<TelemetryEvent> _events = new();
        private readonly HealthMonitor _healthMonitor = new();
        private readonly object _eventsLock = new();
        private long _eventCount;
        private DateTime _startTime = DateTime.UtcNow;

        /// <summary>
        /// Gets the health monitor
        /// </summary>
        public HealthMonitor HealthMonitor => _healthMonitor;

        /// <summary>
        /// Records a counter metric
        /// </summary>
        public void RecordCounter(string name, long value = 1, Dictionary<string, string>? tags = null)
        {
            var key = $"{name}:{TagsKey(tags)}";
            _metrics.AddOrUpdate(key, 
                new MetricValue { Name = name, Value = value, Tags = tags ?? new() },
                (_, existing) => new MetricValue 
                { 
                    Name = name, 
                    Value = existing.Value + value, 
                    Tags = tags ?? new() 
                });
            _eventCount++;
        }

        /// <summary>
        /// Records a gauge metric
        /// </summary>
        public void RecordGauge(string name, double value, Dictionary<string, string>? tags = null)
        {
            var key = $"{name}:{TagsKey(tags)}";
            _gauges[key] = new GaugeValue { Name = name, Value = value, Tags = tags ?? new(), RecordedAt = DateTime.UtcNow };
        }

        /// <summary>
        /// Records a histogram metric
        /// </summary>
        public void RecordHistogram(string name, double value, Dictionary<string, string>? tags = null)
        {
            var key = $"{name}_histogram:{TagsKey(tags)}";
            var metric = _metrics.GetOrAdd(key, new HistogramValue { Name = name, Tags = tags ?? new() });
            if (metric is HistogramValue histogram)
            {
                histogram.Record(value);
            }
        }

        /// <summary>
        /// Records a telemetry event
        /// </summary>
        public void RecordEvent(TelemetryEvent evt)
        {
            lock (_eventsLock)
            {
                _events.Add(evt);
                if (_events.Count > 10000) // Keep last 10k events
                {
                    _events.RemoveRange(0, 1000);
                }
            }
        }

        /// <summary>
        /// Gets metric value
        /// </summary>
        public MetricValue? GetMetric(string name)
        {
            foreach (var kvp in _metrics.Where(kvp => kvp.Value.Name == name))
            {
                return kvp.Value;
            }
            return null;
        }

        /// <summary>
        /// Gets gauge value
        /// </summary>
        public GaugeValue? GetGauge(string name)
        {
            foreach (var kvp in _gauges.Where(kvp => kvp.Value.Name == name))
            {
                return kvp.Value;
            }
            return null;
        }

        /// <summary>
        /// Gets all current metrics
        /// </summary>
        public IReadOnlyCollection<MetricValue> GetAllMetrics()
        {
            return _metrics.Values.ToList().AsReadOnly();
        }

        /// <summary>
        /// Gets all current gauges
        /// </summary>
        public IReadOnlyCollection<GaugeValue> GetAllGauges()
        {
            return _gauges.Values.ToList().AsReadOnly();
        }

        /// <summary>
        /// Gets recent events
        /// </summary>
        public IReadOnlyList<TelemetryEvent> GetRecentEvents(int count = 100)
        {
            lock (_eventsLock)
            {
                var start = Math.Max(0, _events.Count - count);
                return _events.Skip(start).ToList().AsReadOnly();
            }
        }

        /// <summary>
        /// Gets aggregated telemetry snapshot
        /// </summary>
        public AggregatedTelemetrySnapshot GetSnapshot()
        {
            var uptime = DateTime.UtcNow - _startTime;
            var healthSnapshot = _healthMonitor.GetSnapshot();

            // Calculate derived metrics
            var loggedCounter = GetMetric("logs_logged");
            var droppedCounter = GetMetric("logs_dropped");

            return new AggregatedTelemetrySnapshot
            {
                Uptime = uptime,
                CapturedAt = DateTime.UtcNow,
                TotalEvents = _eventCount,
                MetricCount = _metrics.Count,
                GaugeCount = _gauges.Count,
                RecordedMetrics = _metrics.Values.ToList(),
                RecordedGauges = _gauges.Values.ToList(),
                HealthStatus = healthSnapshot,
                LogsLogged = loggedCounter?.Value ?? 0,
                LogsDropped = droppedCounter?.Value ?? 0,
                RecentEventsCount = _events.Count
            };
        }

        /// <summary>
        /// Exports metrics in prometheus-like format
        /// </summary>
        public string ExportPrometheusFormat()
        {
            var lines = new List<string> { "# HELP EonaCat.LogStack telemetry metrics" };

            foreach (var metric in _metrics.Values.GroupBy(m => m.Name))
            {
                lines.Add($"# TYPE {metric.Key} gauge");
                foreach (var m in metric)
                {
                    var tagsStr = m.Tags.Any() ? "{" + string.Join(",", m.Tags.Select(t => $"{t.Key}=\"{t.Value}\"")) + "}" : "";
                    lines.Add($"{metric.Key}{tagsStr} {m.Value}");
                }
            }

            foreach (var gauge in _gauges.Values.GroupBy(g => g.Name))
            {
                lines.Add($"# TYPE {gauge.Key} gauge");
                foreach (var g in gauge)
                {
                    var tagsStr = g.Tags.Any() ? "{" + string.Join(",", g.Tags.Select(t => $"{t.Key}=\"{t.Value}\"")) + "}" : "";
                    lines.Add($"{gauge.Key}{tagsStr} {g.Value}");
                }
            }

            return string.Join("\n", lines);
        }

        /// <summary>
        /// Resets all metrics
        /// </summary>
        public void Reset()
        {
            _metrics.Clear();
            _gauges.Clear();
            lock (_eventsLock)
            {
                _events.Clear();
            }
            _eventCount = 0;
            _startTime = DateTime.UtcNow;
        }

        private static string TagsKey(Dictionary<string, string>? tags)
        {
            if (tags == null || tags.Count == 0) return "";
            return string.Join(",", tags.OrderBy(t => t.Key).Select(t => $"{t.Key}={t.Value}"));
        }
    }

    /// <summary>
    /// Represents a metric value
    /// </summary>
    public class MetricValue
    {
        public string Name { get; set; }
        public long Value { get; set; }
        public Dictionary<string, string> Tags { get; set; } = new();
    }

    /// <summary>
    /// Represents a gauge value
    /// </summary>
    public sealed class GaugeValue
    {
        public string Name { get; set; }
        public double Value { get; set; }
        public Dictionary<string, string> Tags { get; set; } = new();
        public DateTime RecordedAt { get; set; }
    }

    /// <summary>
    /// Histogram metric value
    /// </summary>
    public sealed class HistogramValue : MetricValue
    {
        private readonly List<double> _values = new();
        public IReadOnlyList<double> Values => _values.AsReadOnly();
        public double Min { get; private set; } = double.MaxValue;
        public double Max { get; private set; } = double.MinValue;
        public double Sum { get; private set; }
        public double Mean => Values.Count > 0 ? Sum / Values.Count : 0;
        public double P95 => CalculatePercentile(0.95);
        public double P99 => CalculatePercentile(0.99);

        public void Record(double value)
        {
            _values.Add(value);
            Value++;
            Sum += value;
            if (value < Min) Min = value;
            if (value > Max) Max = value;
            if (_values.Count > 1000) // Keep reasonable history
            {
                _values.RemoveRange(0, 100);
            }
        }

        private double CalculatePercentile(double percentile)
        {
            if (_values.Count == 0) return 0;
            var sorted = _values.OrderBy(v => v).ToList();
            var index = (int)((percentile / 100.0) * sorted.Count);
            index = Math.Min(index, sorted.Count - 1);
            return sorted[index];
        }
    }

    /// <summary>
    /// Aggregated telemetry snapshot
    /// </summary>
    public sealed class AggregatedTelemetrySnapshot
    {
        public TimeSpan Uptime { get; set; }
        public DateTime CapturedAt { get; set; }
        public long TotalEvents { get; set; }
        public int MetricCount { get; set; }
        public int GaugeCount { get; set; }
        public List<MetricValue> RecordedMetrics { get; set; } = new();
        public List<GaugeValue> RecordedGauges { get; set; } = new();
        public HealthSnapshot HealthStatus { get; set; }
        public long LogsLogged { get; set; }
        public long LogsDropped { get; set; }
        public int RecentEventsCount { get; set; }

        public override string ToString()
        {
            return $"Uptime: {Uptime.TotalSeconds:F2}s | Events: {TotalEvents} | Health: {HealthStatus.OverallStatus} | Logged: {LogsLogged} | Dropped: {LogsDropped}";
        }
    }
}
