// ============================================================================
// mb_clouseau - System Metrics Collector
// Uncovering clues with MusicBee Clouseau
// ============================================================================

using System;
using System.Diagnostics;
using System.Management;
using NLog;

namespace MusicBeePlugin.Clouseau.Metrics
{
    /// <summary>
    /// Collects system-wide metrics including CPU, memory, and disk.
    /// Uses PerformanceCounter and WMI for data collection.
    /// </summary>
    public class SystemMetrics : IDisposable
    {
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

        // Performance Counters
        private PerformanceCounter _cpuCounter;
        private PerformanceCounter _availableMemoryCounter;
        private PerformanceCounter _committedBytesCounter;
        private PerformanceCounter[] _perCoreCpuCounters;

        // Cached system info (static, doesn't change)
        private long _totalPhysicalMemoryMB;
        private int _cpuCoreCount;
        private bool _initialized;
        private bool _disposed;

        // Track initialization failures to avoid repeated attempts
        private bool _cpuCounterFailed;
        private bool _memoryCounterFailed;
        private bool _perCoreCountersFailed;

        /// <summary>
        /// Total physical memory in megabytes (cached at startup).
        /// </summary>
        public long TotalPhysicalMemoryMB => _totalPhysicalMemoryMB;

        /// <summary>
        /// Number of logical CPU cores (cached at startup).
        /// </summary>
        public int CpuCoreCount => _cpuCoreCount;

        /// <summary>
        /// Initializes the system metrics collector.
        /// Call this before collecting metrics.
        /// </summary>
        public void Initialize()
        {
            if (_initialized) return;

            try
            {
                Logger.Debug("Initializing SystemMetrics collector...");

                // Get static system info via WMI
                GetSystemInfoFromWMI();

                // Initialize performance counters
                InitializePerformanceCounters();

                _initialized = true;
                Logger.Info("SystemMetrics initialized. CPU cores: {0}, Total RAM: {1}MB",
                    _cpuCoreCount, _totalPhysicalMemoryMB);
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Failed to initialize SystemMetrics");
                // Set defaults so we don't crash
                _cpuCoreCount = Environment.ProcessorCount;
                _totalPhysicalMemoryMB = 0;
                _initialized = true; // Mark as initialized to avoid retry loops
            }
        }

        /// <summary>
        /// Gets static system information from WMI.
        /// </summary>
        private void GetSystemInfoFromWMI()
        {
            try
            {
                // Get total physical memory
                using (var searcher = new ManagementObjectSearcher("SELECT TotalPhysicalMemory FROM Win32_ComputerSystem"))
                {
                    foreach (ManagementObject obj in searcher.Get())
                    {
                        var totalBytes = Convert.ToInt64(obj["TotalPhysicalMemory"]);
                        _totalPhysicalMemoryMB = totalBytes / (1024 * 1024);
                        Logger.Trace("WMI: TotalPhysicalMemory = {0} bytes ({1} MB)", totalBytes, _totalPhysicalMemoryMB);
                        break;
                    }
                }

                // Get CPU core count
                _cpuCoreCount = Environment.ProcessorCount;
                Logger.Trace("CPU cores (Environment.ProcessorCount): {0}", _cpuCoreCount);

                // Alternative: Get from WMI for more detail
                using (var searcher = new ManagementObjectSearcher("SELECT NumberOfLogicalProcessors FROM Win32_Processor"))
                {
                    foreach (ManagementObject obj in searcher.Get())
                    {
                        var cores = Convert.ToInt32(obj["NumberOfLogicalProcessors"]);
                        if (cores > 0)
                        {
                            _cpuCoreCount = cores;
                            Logger.Trace("WMI: NumberOfLogicalProcessors = {0}", cores);
                        }
                        break;
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Warn(ex, "WMI query failed, using fallback values");
                _cpuCoreCount = Environment.ProcessorCount;
                _totalPhysicalMemoryMB = 0;
            }
        }

        /// <summary>
        /// Initializes performance counters.
        /// </summary>
        private void InitializePerformanceCounters()
        {
            // CPU usage counter
            try
            {
                _cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total", true);
                // Prime the counter (first read is always 0)
                _cpuCounter.NextValue();
                Logger.Trace("CPU performance counter initialized");
            }
            catch (Exception ex)
            {
                Logger.Warn(ex, "Failed to create CPU performance counter");
                _cpuCounterFailed = true;
            }

            // Available memory counter
            try
            {
                _availableMemoryCounter = new PerformanceCounter("Memory", "Available MBytes", true);
                _availableMemoryCounter.NextValue();
                Logger.Trace("Available memory counter initialized");
            }
            catch (Exception ex)
            {
                Logger.Warn(ex, "Failed to create available memory counter");
                _memoryCounterFailed = true;
            }

            // Committed bytes in use counter
            try
            {
                _committedBytesCounter = new PerformanceCounter("Memory", "% Committed Bytes In Use", true);
                _committedBytesCounter.NextValue();
                Logger.Trace("Committed bytes counter initialized");
            }
            catch (Exception ex)
            {
                Logger.Warn(ex, "Failed to create committed bytes counter");
            }

            // Per-core CPU counters
            try
            {
                _perCoreCpuCounters = new PerformanceCounter[_cpuCoreCount];
                for (int i = 0; i < _cpuCoreCount; i++)
                {
                    _perCoreCpuCounters[i] = new PerformanceCounter("Processor", "% Processor Time", i.ToString(), true);
                    _perCoreCpuCounters[i].NextValue();
                }
                Logger.Trace("Per-core CPU counters initialized ({0} cores)", _cpuCoreCount);
            }
            catch (Exception ex)
            {
                Logger.Warn(ex, "Failed to create per-core CPU counters");
                _perCoreCountersFailed = true;
                _perCoreCpuCounters = null;
            }
        }

        /// <summary>
        /// Collects current system metrics and populates the snapshot.
        /// </summary>
        /// <param name="snapshot">The snapshot to populate with system metrics.</param>
        public void Collect(MetricsSnapshot snapshot)
        {
            if (!_initialized)
            {
                Initialize();
            }

            try
            {
                // CPU usage
                snapshot.CpuPercent = GetCpuPercent();
                snapshot.CpuCoreCount = _cpuCoreCount;

                // Per-core CPU (if available)
                snapshot.PerCoreCpuPercent = GetPerCoreCpuPercent();

                // Memory
                snapshot.AvailableMemoryMB = GetAvailableMemoryMB();
                snapshot.TotalMemoryMB = _totalPhysicalMemoryMB;

                // Calculate memory percent
                if (_totalPhysicalMemoryMB > 0)
                {
                    var usedMB = _totalPhysicalMemoryMB - snapshot.AvailableMemoryMB;
                    snapshot.MemoryPercent = (float)(usedMB * 100.0 / _totalPhysicalMemoryMB);
                }
                else
                {
                    snapshot.MemoryPercent = GetCommittedBytesPercent();
                }

                // Low memory warning (less than 10% or 500MB available)
                snapshot.LowMemoryWarning =
                    snapshot.AvailableMemoryMB < 500 ||
                    (_totalPhysicalMemoryMB > 0 && snapshot.AvailableMemoryMB < _totalPhysicalMemoryMB * 0.1);
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Error collecting system metrics");
            }
        }

        /// <summary>
        /// Gets the current CPU usage percentage.
        /// </summary>
        private float GetCpuPercent()
        {
            if (_cpuCounterFailed || _cpuCounter == null)
                return 0;

            try
            {
                return _cpuCounter.NextValue();
            }
            catch (Exception ex)
            {
                Logger.Trace(ex, "Error reading CPU counter");
                return 0;
            }
        }

        /// <summary>
        /// Gets per-core CPU usage percentages.
        /// </summary>
        private float[] GetPerCoreCpuPercent()
        {
            if (_perCoreCountersFailed || _perCoreCpuCounters == null)
                return Array.Empty<float>();

            try
            {
                var values = new float[_perCoreCpuCounters.Length];
                for (int i = 0; i < _perCoreCpuCounters.Length; i++)
                {
                    values[i] = _perCoreCpuCounters[i].NextValue();
                }
                return values;
            }
            catch (Exception ex)
            {
                Logger.Trace(ex, "Error reading per-core CPU counters");
                return Array.Empty<float>();
            }
        }

        /// <summary>
        /// Gets available physical memory in megabytes.
        /// </summary>
        private long GetAvailableMemoryMB()
        {
            if (_memoryCounterFailed || _availableMemoryCounter == null)
            {
                // Fallback: try WMI
                return GetAvailableMemoryFromWMI();
            }

            try
            {
                return (long)_availableMemoryCounter.NextValue();
            }
            catch (Exception ex)
            {
                Logger.Trace(ex, "Error reading available memory counter");
                return GetAvailableMemoryFromWMI();
            }
        }

        /// <summary>
        /// Gets available memory from WMI (fallback method).
        /// </summary>
        private long GetAvailableMemoryFromWMI()
        {
            try
            {
                using (var searcher = new ManagementObjectSearcher("SELECT FreePhysicalMemory FROM Win32_OperatingSystem"))
                {
                    foreach (ManagementObject obj in searcher.Get())
                    {
                        // FreePhysicalMemory is in KB
                        var freeKB = Convert.ToInt64(obj["FreePhysicalMemory"]);
                        return freeKB / 1024;
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Trace(ex, "WMI fallback for available memory failed");
            }
            return 0;
        }

        /// <summary>
        /// Gets the committed bytes in use percentage.
        /// </summary>
        private float GetCommittedBytesPercent()
        {
            if (_committedBytesCounter == null)
                return 0;

            try
            {
                return _committedBytesCounter.NextValue();
            }
            catch (Exception ex)
            {
                Logger.Trace(ex, "Error reading committed bytes counter");
                return 0;
            }
        }

        /// <summary>
        /// Gets disk space information for a specific drive.
        /// </summary>
        /// <param name="driveLetter">Drive letter (e.g., "C")</param>
        /// <returns>Tuple of (FreeSpaceMB, TotalSpaceMB)</returns>
        public (long FreeSpaceMB, long TotalSpaceMB) GetDiskSpace(string driveLetter)
        {
            try
            {
                var query = $"SELECT FreeSpace, Size FROM Win32_LogicalDisk WHERE DeviceID = '{driveLetter}:'";
                using (var searcher = new ManagementObjectSearcher(query))
                {
                    foreach (ManagementObject obj in searcher.Get())
                    {
                        var freeSpace = Convert.ToInt64(obj["FreeSpace"]) / (1024 * 1024);
                        var totalSize = Convert.ToInt64(obj["Size"]) / (1024 * 1024);
                        return (freeSpace, totalSize);
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Trace(ex, "Error getting disk space for drive {0}", driveLetter);
            }
            return (0, 0);
        }

        /// <summary>
        /// Disposes resources.
        /// </summary>
        public void Dispose()
        {
            if (_disposed) return;
            _disposed = true;

            try
            {
                _cpuCounter?.Dispose();
                _availableMemoryCounter?.Dispose();
                _committedBytesCounter?.Dispose();

                if (_perCoreCpuCounters != null)
                {
                    foreach (var counter in _perCoreCpuCounters)
                    {
                        counter?.Dispose();
                    }
                }

                Logger.Debug("SystemMetrics disposed");
            }
            catch (Exception ex)
            {
                Logger.Warn(ex, "Error disposing SystemMetrics");
            }
        }
    }
}
