using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Serilog;

namespace ZB.MOM.WW.LmxProxy.Host.Services
{
    /// <summary>
    ///     Provides performance metrics tracking for LmxProxy operations
    /// </summary>
    public class PerformanceMetrics : IDisposable
    {
        private static readonly ILogger Logger = Log.ForContext<PerformanceMetrics>();

        private readonly ConcurrentDictionary<string, OperationMetrics> _metrics = new();
        private readonly Timer _reportingTimer;
        private bool _disposed;

        /// <summary>
        ///     Initializes a new instance of the PerformanceMetrics class
        /// </summary>
        public PerformanceMetrics()
        {
            // Report metrics every minute
            _reportingTimer = new Timer(ReportMetrics, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
        }

        public void Dispose()
        {
            if (_disposed)
            {
                return;
            }

            _disposed = true;

            _reportingTimer?.Dispose();
            ReportMetrics(null); // Final report
        }

        /// <summary>
        ///     Records the execution time of an operation
        /// </summary>
        public void RecordOperation(string operationName, TimeSpan duration, bool success = true)
        {
            OperationMetrics? metrics = _metrics.GetOrAdd(operationName, _ => new OperationMetrics());
            metrics.Record(duration, success);
        }

        /// <summary>
        ///     Creates a timing scope for measuring operation duration
        /// </summary>
        public ITimingScope BeginOperation(string operationName) => new TimingScope(this, operationName);

        /// <summary>
        ///     Gets current metrics for a specific operation
        /// </summary>
        public OperationMetrics? GetMetrics(string operationName) =>
            _metrics.TryGetValue(operationName, out OperationMetrics? metrics) ? metrics : null;

        /// <summary>
        ///     Gets all current metrics
        /// </summary>
        public IReadOnlyDictionary<string, OperationMetrics> GetAllMetrics() =>
            _metrics.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

        /// <summary>
        ///     Gets statistics for all operations
        /// </summary>
        public Dictionary<string, MetricsStatistics> GetStatistics() =>
            _metrics.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.GetStatistics());

        private void ReportMetrics(object? state)
        {
            foreach (KeyValuePair<string, OperationMetrics> kvp in _metrics)
            {
                MetricsStatistics stats = kvp.Value.GetStatistics();
                if (stats.TotalCount > 0)
                {
                    Logger.Information(
                        "Performance Metrics - {Operation}: Count={Count}, Success={SuccessRate:P}, " +
                        "Avg={AverageMs:F2}ms, Min={MinMs:F2}ms, Max={MaxMs:F2}ms, P95={P95Ms:F2}ms",
                        kvp.Key,
                        stats.TotalCount,
                        stats.SuccessRate,
                        stats.AverageMilliseconds,
                        stats.MinMilliseconds,
                        stats.MaxMilliseconds,
                        stats.Percentile95Milliseconds);
                }
            }
        }

        /// <summary>
        ///     Timing scope for automatic duration measurement
        /// </summary>
        public interface ITimingScope : IDisposable
        {
            void SetSuccess(bool success);
        }

        private class TimingScope : ITimingScope
        {
            private readonly PerformanceMetrics _metrics;
            private readonly string _operationName;
            private readonly Stopwatch _stopwatch;
            private bool _disposed;
            private bool _success = true;

            public TimingScope(PerformanceMetrics metrics, string operationName)
            {
                _metrics = metrics;
                _operationName = operationName;
                _stopwatch = Stopwatch.StartNew();
            }

            public void SetSuccess(bool success) => _success = success;

            public void Dispose()
            {
                if (_disposed)
                {
                    return;
                }

                _disposed = true;

                _stopwatch.Stop();
                _metrics.RecordOperation(_operationName, _stopwatch.Elapsed, _success);
            }
        }
    }

    /// <summary>
    ///     Metrics for a specific operation
    /// </summary>
    public class OperationMetrics
    {
        private readonly List<double> _durations = new();
        private readonly object _lock = new();
        private double _maxMilliseconds;
        private double _minMilliseconds = double.MaxValue;
        private long _successCount;
        private long _totalCount;
        private double _totalMilliseconds;

        public void Record(TimeSpan duration, bool success)
        {
            lock (_lock)
            {
                double ms = duration.TotalMilliseconds;
                _durations.Add(ms);
                _totalCount++;
                if (success)
                {
                    _successCount++;
                }

                _totalMilliseconds += ms;
                _minMilliseconds = Math.Min(_minMilliseconds, ms);
                _maxMilliseconds = Math.Max(_maxMilliseconds, ms);

                // Keep only last 1000 samples for percentile calculation
                if (_durations.Count > 1000)
                {
                    _durations.RemoveAt(0);
                }
            }
        }

        public MetricsStatistics GetStatistics()
        {
            lock (_lock)
            {
                if (_totalCount == 0)
                {
                    return new MetricsStatistics();
                }

                var sortedDurations = _durations.OrderBy(d => d).ToList();
                int p95Index = (int)Math.Ceiling(sortedDurations.Count * 0.95) - 1;

                return new MetricsStatistics
                {
                    TotalCount = _totalCount,
                    SuccessCount = _successCount,
                    SuccessRate = _successCount / (double)_totalCount,
                    AverageMilliseconds = _totalMilliseconds / _totalCount,
                    MinMilliseconds = _minMilliseconds == double.MaxValue ? 0 : _minMilliseconds,
                    MaxMilliseconds = _maxMilliseconds,
                    Percentile95Milliseconds = sortedDurations.Count > 0 ? sortedDurations[Math.Max(0, p95Index)] : 0
                };
            }
        }
    }

    /// <summary>
    ///     Statistics for an operation
    /// </summary>
    public class MetricsStatistics
    {
        public long TotalCount { get; set; }
        public long SuccessCount { get; set; }
        public double SuccessRate { get; set; }
        public double AverageMilliseconds { get; set; }
        public double MinMilliseconds { get; set; }
        public double MaxMilliseconds { get; set; }
        public double Percentile95Milliseconds { get; set; }
    }
}
