// ============================================================================
// mb_clouseau - Process Metrics Collector
// Uncovering clues with MusicBee Clouseau
//
// CRITICAL: This is the most important collector for detecting memory and
// handle leaks in MusicBee and plugins!
// ============================================================================

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using NLog;

namespace MusicBeePlugin.Clouseau.Metrics
{
    // Handle type enumeration for GetGuiResources
    internal enum GuiResourceType : uint
    {
        GDI_OBJECTS = 0,
        USER_OBJECTS = 1,
        GDI_OBJECTS_PEAK = 2,
        USER_OBJECTS_PEAK = 4
    }

    /// <summary>
    /// Collects process-level metrics for MusicBee.exe and self-monitoring.
    /// This is the CRITICAL collector for detecting memory leaks and handle leaks!
    ///
    /// Key metrics to watch:
    /// - PrivateBytes: Steadily increasing = MEMORY LEAK
    /// - HandleCount: Steadily increasing = HANDLE LEAK
    /// </summary>
    public class ProcessMetrics : IDisposable
    {
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

        // Win32 API for handle type enumeration
        [DllImport("user32.dll")]
        private static extern uint GetGuiResources(IntPtr hProcess, uint uiFlags);

        private const string MusicBeeProcessName = "MusicBee";

        // Cached process references
        private Process _musicBeeProcess;
        private Process _selfProcess;
        private int _lastMusicBeePid = -1;

        // Previous sample for delta calculations
        private int _prevHandleCount;
        private long _prevPrivateBytes;
        private long _prevWorkingSet;
        private int _prevThreadCount;
        private int _prevGdiObjects;
        private int _prevUserObjects;
        private int _prevKernelHandles;

        // Trend tracking for leak detection
        private const int TrendWindowSize = 12; // 12 samples = 1 minute at 5s intervals
        private readonly int[] _handleDeltas = new int[TrendWindowSize];
        private readonly long[] _privateBytesDeltas = new long[TrendWindowSize];
        private readonly int[] _gdiDeltas = new int[TrendWindowSize];
        private readonly int[] _userDeltas = new int[TrendWindowSize];
        private readonly int[] _kernelDeltas = new int[TrendWindowSize];
        private int _trendIndex;
        private int _sampleCount;

        // Leak detection thresholds
        private const int HandleLeakThreshold = 10;        // Handles gained per minute
        private const long MemoryLeakThreshold = 5 * 1024 * 1024; // 5MB per minute

        private bool _initialized;
        private bool _disposed;

        /// <summary>
        /// Gets the currently tracked MusicBee process (if found).
        /// </summary>
        public Process MusicBeeProcess => _musicBeeProcess;

        /// <summary>
        /// Initializes the process metrics collector.
        /// </summary>
        public void Initialize()
        {
            if (_initialized) return;

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

                // Get self process (the plugin runs in MusicBee's process)
                _selfProcess = Process.GetCurrentProcess();
                Logger.Debug("Self process: {0} (PID: {1})", _selfProcess.ProcessName, _selfProcess.Id);

                // Find MusicBee process
                RefreshMusicBeeProcess();

                _initialized = true;
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Failed to initialize ProcessMetrics");
                _initialized = true;
            }
        }

        /// <summary>
        /// Attempts to find or refresh the MusicBee process reference.
        /// </summary>
        /// <returns>True if MusicBee process was found.</returns>
        public bool RefreshMusicBeeProcess()
        {
            try
            {
                // Check if current reference is still valid
                if (_musicBeeProcess != null && !_musicBeeProcess.HasExited)
                {
                    return true;
                }

                // Find MusicBee process
                var processes = Process.GetProcessesByName(MusicBeeProcessName);
                if (processes.Length > 0)
                {
                    _musicBeeProcess = processes[0];

                    // Log if PID changed (restart detected)
                    if (_lastMusicBeePid != -1 && _lastMusicBeePid != _musicBeeProcess.Id)
                    {
                        Logger.Warn("MusicBee process changed! Old PID: {0}, New PID: {1}",
                            _lastMusicBeePid, _musicBeeProcess.Id);
                        ResetTrendTracking();
                    }

                    _lastMusicBeePid = _musicBeeProcess.Id;
                    Logger.Debug("Found MusicBee process (PID: {0})", _musicBeeProcess.Id);

                    // Dispose other process references
                    for (int i = 1; i < processes.Length; i++)
                    {
                        processes[i].Dispose();
                    }

                    return true;
                }

                Logger.Warn("MusicBee process not found!");
                _musicBeeProcess = null;
                return false;
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Error refreshing MusicBee process");
                return false;
            }
        }

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

            // Collect MusicBee metrics
            CollectMusicBeeMetrics(snapshot);

            // Collect self metrics (for the plugin)
            CollectSelfMetrics(snapshot);

            // Calculate deltas and trends
            CalculateDeltas(snapshot);

            // Check for leaks
            DetectLeaks(snapshot);
        }

        /// <summary>
        /// Collects metrics for the MusicBee process.
        /// </summary>
        private void CollectMusicBeeMetrics(MetricsSnapshot snapshot)
        {
            // Ensure we have a valid process reference
            if (!RefreshMusicBeeProcess())
            {
                snapshot.MBProcessFound = false;
                return;
            }

            try
            {
                // Refresh process info
                _musicBeeProcess.Refresh();

                snapshot.MBProcessFound = true;
                snapshot.MBProcessId = _musicBeeProcess.Id;

                // Memory metrics
                snapshot.MBWorkingSetBytes = _musicBeeProcess.WorkingSet64;
                snapshot.MBWorkingSetMB = snapshot.MBWorkingSetBytes / (1024 * 1024);

                snapshot.MBPrivateBytesRaw = _musicBeeProcess.PrivateMemorySize64;
                snapshot.MBPrivateBytesMB = snapshot.MBPrivateBytesRaw / (1024 * 1024);

                snapshot.MBVirtualMemoryMB = _musicBeeProcess.VirtualMemorySize64 / (1024 * 1024);

                // Handle and thread counts - KEY LEAK INDICATORS!
                snapshot.MBHandleCount = _musicBeeProcess.HandleCount;
                snapshot.MBThreadCount = _musicBeeProcess.Threads.Count;

                // Get handle type breakdown using GetGuiResources
                try
                {
                    var processHandle = _musicBeeProcess.Handle;
                    snapshot.GdiObjectCount = (int)GetGuiResources(processHandle, (uint)GuiResourceType.GDI_OBJECTS);
                    snapshot.UserObjectCount = (int)GetGuiResources(processHandle, (uint)GuiResourceType.USER_OBJECTS);
                    snapshot.GdiObjectPeak = (int)GetGuiResources(processHandle, (uint)GuiResourceType.GDI_OBJECTS_PEAK);
                    snapshot.UserObjectPeak = (int)GetGuiResources(processHandle, (uint)GuiResourceType.USER_OBJECTS_PEAK);

                    // Calculate "other" handles (kernel handles = total - GDI - USER)
                    snapshot.KernelHandleCount = snapshot.MBHandleCount - snapshot.GdiObjectCount - snapshot.UserObjectCount;
                    if (snapshot.KernelHandleCount < 0) snapshot.KernelHandleCount = 0;
                }
                catch (Exception ex)
                {
                    Logger.Trace("Error getting GUI resources: {0}", ex.Message);
                }

                // CPU time
                snapshot.MBCpuTime = _musicBeeProcess.TotalProcessorTime;
                snapshot.MBUserCpuTime = _musicBeeProcess.UserProcessorTime;
                snapshot.MBPrivilegedCpuTime = _musicBeeProcess.PrivilegedProcessorTime;

                Logger.Trace("MusicBee metrics: WS={0}MB, PB={1}MB, Handles={2} (GDI={3}, USER={4}, Kernel={5}), Threads={6}",
                    snapshot.MBWorkingSetMB, snapshot.MBPrivateBytesMB,
                    snapshot.MBHandleCount, snapshot.GdiObjectCount, snapshot.UserObjectCount,
                    snapshot.KernelHandleCount, snapshot.MBThreadCount);
            }
            catch (InvalidOperationException)
            {
                // Process has exited
                Logger.Warn("MusicBee process has exited");
                snapshot.MBProcessFound = false;
                _musicBeeProcess = null;
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Error collecting MusicBee process metrics");
                snapshot.MBProcessFound = false;
            }
        }

        /// <summary>
        /// Collects metrics for the current process (self-monitoring).
        /// Since the plugin runs inside MusicBee, this is actually the same process.
        /// </summary>
        private void CollectSelfMetrics(MetricsSnapshot snapshot)
        {
            try
            {
                if (_selfProcess != null)
                {
                    _selfProcess.Refresh();

                    snapshot.SelfWorkingSetMB = _selfProcess.WorkingSet64 / (1024 * 1024);
                    snapshot.SelfPrivateBytesMB = _selfProcess.PrivateMemorySize64 / (1024 * 1024);
                    snapshot.SelfHandleCount = _selfProcess.HandleCount;
                    snapshot.SelfThreadCount = _selfProcess.Threads.Count;
                }
            }
            catch (Exception ex)
            {
                Logger.Trace(ex, "Error collecting self process metrics");
            }
        }

        /// <summary>
        /// Calculates deltas between current and previous samples.
        /// </summary>
        private void CalculateDeltas(MetricsSnapshot snapshot)
        {
            if (_sampleCount > 0)
            {
                // Calculate deltas from previous sample
                snapshot.HandleCountDelta = snapshot.MBHandleCount - _prevHandleCount;
                snapshot.PrivateBytesDelta = snapshot.MBPrivateBytesRaw - _prevPrivateBytes;
                snapshot.WorkingSetDelta = snapshot.MBWorkingSetBytes - _prevWorkingSet;
                snapshot.ThreadCountDelta = snapshot.MBThreadCount - _prevThreadCount;

                // Calculate handle type deltas - KEY for leak source identification!
                snapshot.GdiObjectDelta = snapshot.GdiObjectCount - _prevGdiObjects;
                snapshot.UserObjectDelta = snapshot.UserObjectCount - _prevUserObjects;
                snapshot.KernelHandleDelta = snapshot.KernelHandleCount - _prevKernelHandles;

                // Store in trend window
                _handleDeltas[_trendIndex] = snapshot.HandleCountDelta;
                _privateBytesDeltas[_trendIndex] = snapshot.PrivateBytesDelta;
                _gdiDeltas[_trendIndex] = snapshot.GdiObjectDelta;
                _userDeltas[_trendIndex] = snapshot.UserObjectDelta;
                _kernelDeltas[_trendIndex] = snapshot.KernelHandleDelta;

                // Update trend index (circular buffer)
                _trendIndex = (_trendIndex + 1) % TrendWindowSize;
            }

            // Store current values for next delta calculation
            _prevHandleCount = snapshot.MBHandleCount;
            _prevPrivateBytes = snapshot.MBPrivateBytesRaw;
            _prevWorkingSet = snapshot.MBWorkingSetBytes;
            _prevThreadCount = snapshot.MBThreadCount;
            _prevGdiObjects = snapshot.GdiObjectCount;
            _prevUserObjects = snapshot.UserObjectCount;
            _prevKernelHandles = snapshot.KernelHandleCount;

            _sampleCount++;
            snapshot.TrendSampleCount = Math.Min(_sampleCount, TrendWindowSize);

            // Calculate cumulative trends
            CalculateTrendTotals(snapshot);
        }

        /// <summary>
        /// Calculates cumulative trend totals from the trend window.
        /// </summary>
        private void CalculateTrendTotals(MetricsSnapshot snapshot)
        {
            int handleTotal = 0;
            long privateBytesTotal = 0;

            int samplesToUse = Math.Min(_sampleCount, TrendWindowSize);
            for (int i = 0; i < samplesToUse; i++)
            {
                handleTotal += _handleDeltas[i];
                privateBytesTotal += _privateBytesDeltas[i];
            }

            snapshot.HandleCountTrendTotal = handleTotal;
            snapshot.PrivateBytesTrendTotal = privateBytesTotal;
        }

        /// <summary>
        /// Detects potential leaks based on trend analysis.
        /// </summary>
        private void DetectLeaks(MetricsSnapshot snapshot)
        {
            // Need at least half the window filled for reliable detection
            if (_sampleCount < TrendWindowSize / 2)
                return;

            int samplesToUse = Math.Min(_sampleCount, TrendWindowSize);

            // Handle leak detection:
            // If handles consistently increase over the trend window, flag it
            int positiveHandleDeltas = 0;
            int positiveGdiDeltas = 0;
            int positiveUserDeltas = 0;
            int positiveKernelDeltas = 0;
            int gdiTrendTotal = 0;
            int userTrendTotal = 0;
            int kernelTrendTotal = 0;

            for (int i = 0; i < samplesToUse; i++)
            {
                if (_handleDeltas[i] > 0) positiveHandleDeltas++;
                if (_gdiDeltas[i] > 0) positiveGdiDeltas++;
                if (_userDeltas[i] > 0) positiveUserDeltas++;
                if (_kernelDeltas[i] > 0) positiveKernelDeltas++;
                gdiTrendTotal += _gdiDeltas[i];
                userTrendTotal += _userDeltas[i];
                kernelTrendTotal += _kernelDeltas[i];
            }

            // If >75% of samples show handle increase AND total increase exceeds threshold
            if (positiveHandleDeltas > TrendWindowSize * 0.75 &&
                snapshot.HandleCountTrendTotal > HandleLeakThreshold)
            {
                snapshot.HandleLeakSuspected = true;

                // Identify the SOURCE of the leak
                var leakSource = IdentifyLeakSource(
                    gdiTrendTotal, userTrendTotal, kernelTrendTotal,
                    positiveGdiDeltas, positiveUserDeltas, positiveKernelDeltas, samplesToUse);

                Logger.Warn("HANDLE LEAK SUSPECTED! Handles +{0} over {1} samples. SOURCE: {2}",
                    snapshot.HandleCountTrendTotal, snapshot.TrendSampleCount, leakSource);

                Logger.Warn("  Breakdown: GDI={0:+#;-#;0} (peak={1}), USER={2:+#;-#;0} (peak={3}), Kernel={4:+#;-#;0}",
                    gdiTrendTotal, snapshot.GdiObjectPeak,
                    userTrendTotal, snapshot.UserObjectPeak,
                    kernelTrendTotal);
            }

            // Memory leak detection:
            // If private bytes consistently increase over the trend window, flag it
            int positiveMemoryDeltas = 0;
            for (int i = 0; i < samplesToUse; i++)
            {
                if (_privateBytesDeltas[i] > 0) positiveMemoryDeltas++;
            }

            // If >75% of samples show memory increase AND total increase exceeds threshold
            if (positiveMemoryDeltas > TrendWindowSize * 0.75 &&
                snapshot.PrivateBytesTrendTotal > MemoryLeakThreshold)
            {
                snapshot.MemoryLeakSuspected = true;
                Logger.Warn("MEMORY LEAK SUSPECTED! Private bytes increased by {0:N0} bytes over {1} samples",
                    snapshot.PrivateBytesTrendTotal, snapshot.TrendSampleCount);
            }
        }

        /// <summary>
        /// Identifies the likely source of a handle leak based on handle type trends.
        /// </summary>
        private string IdentifyLeakSource(int gdiTotal, int userTotal, int kernelTotal,
            int gdiPositive, int userPositive, int kernelPositive, int sampleCount)
        {
            var sources = new System.Collections.Generic.List<string>();
            double threshold = sampleCount * 0.5; // 50% of samples showing increase

            // Check each handle type for leak patterns
            if (gdiTotal > 3 && gdiPositive > threshold)
            {
                sources.Add($"GDI objects +{gdiTotal} (fonts/brushes/pens/bitmaps/DCs - graphics code not disposing)");
            }
            if (userTotal > 3 && userPositive > threshold)
            {
                sources.Add($"USER objects +{userTotal} (windows/menus/cursors/icons - UI controls not destroyed)");
            }
            if (kernelTotal > 5 && kernelPositive > threshold)
            {
                sources.Add($"Kernel handles +{kernelTotal} (files/registry/events/mutexes - I/O or sync not closed)");
            }

            if (sources.Count == 0)
            {
                // No clear pattern, report all
                return $"Mixed (GDI={gdiTotal:+#;-#;0}, USER={userTotal:+#;-#;0}, Kernel={kernelTotal:+#;-#;0})";
            }

            return string.Join("; ", sources);
        }

        /// <summary>
        /// Resets trend tracking (e.g., when process restarts).
        /// </summary>
        private void ResetTrendTracking()
        {
            Array.Clear(_handleDeltas, 0, _handleDeltas.Length);
            Array.Clear(_privateBytesDeltas, 0, _privateBytesDeltas.Length);
            Array.Clear(_gdiDeltas, 0, _gdiDeltas.Length);
            Array.Clear(_userDeltas, 0, _userDeltas.Length);
            Array.Clear(_kernelDeltas, 0, _kernelDeltas.Length);
            _trendIndex = 0;
            _sampleCount = 0;
            _prevHandleCount = 0;
            _prevPrivateBytes = 0;
            _prevWorkingSet = 0;
            _prevThreadCount = 0;
            _prevGdiObjects = 0;
            _prevUserObjects = 0;
            _prevKernelHandles = 0;
            Logger.Debug("Trend tracking reset");
        }

        /// <summary>
        /// Gets a diagnostic summary of the current process state.
        /// </summary>
        public string GetDiagnosticSummary()
        {
            if (_musicBeeProcess == null || _musicBeeProcess.HasExited)
            {
                return "MusicBee process not found or has exited";
            }

            try
            {
                _musicBeeProcess.Refresh();
                return $@"MusicBee Process Diagnostics (PID: {_musicBeeProcess.Id})
Working Set: {_musicBeeProcess.WorkingSet64 / (1024 * 1024)}MB
Private Bytes: {_musicBeeProcess.PrivateMemorySize64 / (1024 * 1024)}MB
Virtual Memory: {_musicBeeProcess.VirtualMemorySize64 / (1024 * 1024)}MB
Handle Count: {_musicBeeProcess.HandleCount}
Thread Count: {_musicBeeProcess.Threads.Count}
Total CPU Time: {_musicBeeProcess.TotalProcessorTime}
Start Time: {_musicBeeProcess.StartTime}
Uptime: {DateTime.Now - _musicBeeProcess.StartTime}";
            }
            catch (Exception ex)
            {
                return $"Error getting process diagnostics: {ex.Message}";
            }
        }

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

            try
            {
                // Note: Don't dispose _selfProcess as it's the current process
                // and would cause issues
                _musicBeeProcess?.Dispose();
                Logger.Debug("ProcessMetrics disposed");
            }
            catch (Exception ex)
            {
                Logger.Warn(ex, "Error disposing ProcessMetrics");
            }
        }
    }
}
