// ============================================================================
// mb_clouseau - Audio Metrics Collector
// Uncovering clues with MusicBee Clouseau
//
// Complete audio subsystem metrics with full MusicBee API integration
// ============================================================================

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using NLog;
using MusicBeePlugin.Clouseau.Core;

namespace MusicBeePlugin.Clouseau.Metrics
{
    /// <summary>
    /// Collects comprehensive audio subsystem metrics using the MusicBee API.
    /// Tracks output devices, audio properties, DSP state, and optionally spectrum/waveform data.
    /// </summary>
    public class AudioMetrics : IDisposable
    {
        private static readonly Logger Logger = NLog.LogManager.GetCurrentClassLogger();

        // MusicBee API reference
        private Plugin.MusicBeeApiInterface _mbApi;
        private bool _hasApiAccess;

        // Current state
        private AudioState _currentState;
        private AudioState _previousState;
        private readonly object _stateLock = new object();

        // Spectrum/Waveform buffers (preallocated for performance)
        private float[] _spectrumBuffer;
        private float[] _waveformBuffer;
        private float[] _peakBuffer;

        // History for analysis
        private readonly ConcurrentQueue<SpectrumSnapshot> _spectrumHistory;
        private readonly ConcurrentQueue<WaveformSnapshot> _waveformHistory;
        private const int MaxHistorySize = 120;  // 2 min at 1/sec

        // Configuration
        private bool _captureSpectrum;
        private bool _captureWaveform;
        private int _spectrumBinCount = 512;
        private int _waveformSampleCount = 512;

        private bool _initialized;
        private bool _disposed;

        /// <summary>
        /// Event raised when audio state changes (device, volume, DSP).
        /// </summary>
        public event EventHandler<AudioStateChangedEventArgs> StateChanged;

        /// <summary>
        /// Event raised when output device changes.
        /// </summary>
        public event EventHandler<OutputDeviceChangedEventArgs> OutputDeviceChanged;

        /// <summary>
        /// Gets the current audio state.
        /// </summary>
        public AudioState CurrentState
        {
            get
            {
                lock (_stateLock)
                {
                    return _currentState;
                }
            }
        }

        /// <summary>
        /// Gets whether the MusicBee API is connected.
        /// </summary>
        public bool HasApiAccess => _hasApiAccess;

        /// <summary>
        /// Gets or sets whether to capture spectrum data.
        /// </summary>
        public bool CaptureSpectrum
        {
            get => _captureSpectrum;
            set => _captureSpectrum = value;
        }

        /// <summary>
        /// Gets or sets whether to capture waveform data.
        /// </summary>
        public bool CaptureWaveform
        {
            get => _captureWaveform;
            set => _captureWaveform = value;
        }

        public AudioMetrics()
        {
            _spectrumHistory = new ConcurrentQueue<SpectrumSnapshot>();
            _waveformHistory = new ConcurrentQueue<WaveformSnapshot>();
            _currentState = new AudioState { Timestamp = DateTime.Now };
        }

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

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

                // Initialize state with defaults
                _currentState = new AudioState
                {
                    ActiveDevice = "Unknown",
                    AvailableDevices = new string[0],
                    Timestamp = DateTime.Now
                };

                _initialized = true;

                if (_hasApiAccess)
                {
                    Logger.Info("AudioMetrics initialized with MusicBee API integration");
                    RefreshAllState();
                }
                else
                {
                    Logger.Info("AudioMetrics initialized (awaiting MusicBee API connection)");
                }
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Failed to initialize AudioMetrics");
                _initialized = true;
            }
        }

        /// <summary>
        /// Sets the MusicBee API interface. MUST be called by Plugin.cs after initialization.
        /// </summary>
        /// <param name="mbApi">The MusicBee API interface.</param>
        public void SetMusicBeeApi(Plugin.MusicBeeApiInterface mbApi)
        {
            _mbApi = mbApi;
            _hasApiAccess = true;
            Logger.Info("AudioMetrics: MusicBee API connected");

            // Initial state capture
            if (_initialized)
            {
                RefreshAllState();
            }
        }

        /// <summary>
        /// Refreshes all audio state from the MusicBee API.
        /// </summary>
        public void RefreshAllState()
        {
            if (!_hasApiAccess || _mbApi.Equals(default(Plugin.MusicBeeApiInterface))) return;

            var newState = new AudioState { Timestamp = DateTime.Now };

            try
            {
                // === Output Devices ===
                try
                {
                    if (_mbApi.Player_GetOutputDevices != null)
                    {
                        string[] devices = null;
                        string active = null;
                        if (_mbApi.Player_GetOutputDevices(out devices, out active))
                        {
                            newState.AvailableDevices = devices ?? new string[0];
                            newState.ActiveDevice = active ?? "Unknown";
                            newState.DeviceEnumerationTime = DateTime.Now;
                        }
                    }
                }
                catch (Exception ex)
                {
                    Logger.Trace(ex, "Error getting output devices (API may not be available)");
                }

                // === Volume/Mute ===
                newState.Volume = _mbApi.Player_GetVolume?.Invoke() ?? 0f;
                newState.IsMuted = _mbApi.Player_GetMute?.Invoke() ?? false;

                // === DSP State ===
                newState.DspEnabled = _mbApi.Player_GetDspEnabled?.Invoke() ?? false;
                newState.EqualizerEnabled = _mbApi.Player_GetEqualiserEnabled?.Invoke() ?? false;
                newState.CrossfadeEnabled = _mbApi.Player_GetCrossfade?.Invoke() ?? false;
                newState.ReplayGainMode = _mbApi.Player_GetReplayGainMode?.Invoke() ?? Plugin.ReplayGainMode.Off;

                // === Current Track Audio Properties ===
                var playState = _mbApi.Player_GetPlayState?.Invoke() ?? Plugin.PlayState.Undefined;
                if (playState == Plugin.PlayState.Playing || playState == Plugin.PlayState.Paused)
                {
                    var sampleRateStr = _mbApi.NowPlaying_GetFileProperty?.Invoke(Plugin.FilePropertyType.SampleRate);
                    var channelsStr = _mbApi.NowPlaying_GetFileProperty?.Invoke(Plugin.FilePropertyType.Channels);
                    var bitrateStr = _mbApi.NowPlaying_GetFileProperty?.Invoke(Plugin.FilePropertyType.Bitrate);

                    if (int.TryParse(sampleRateStr, out var sr)) newState.SampleRate = sr;
                    if (int.TryParse(channelsStr, out var ch)) newState.Channels = ch;

                    newState.Format = _mbApi.NowPlaying_GetFileProperty?.Invoke(Plugin.FilePropertyType.Format);
                    newState.Bitrate = bitrateStr;
                    newState.ReplayGainTrack = _mbApi.NowPlaying_GetFileProperty?.Invoke(Plugin.FilePropertyType.ReplayGainTrack);
                    newState.ReplayGainAlbum = _mbApi.NowPlaying_GetFileProperty?.Invoke(Plugin.FilePropertyType.ReplayGainAlbum);
                }

                // === Detect Changes and Fire Events ===
                DetectAndNotifyChanges(_currentState, newState);

                lock (_stateLock)
                {
                    _previousState = _currentState;
                    _currentState = newState;
                }
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Error refreshing audio state");
            }
        }

        /// <summary>
        /// Detects state changes and fires appropriate events.
        /// </summary>
        private void DetectAndNotifyChanges(AudioState oldState, AudioState newState)
        {
            if (oldState == null) return;

            // Device change
            if (oldState.ActiveDevice != newState.ActiveDevice)
            {
                Logger.Info($"Output device changed: '{oldState.ActiveDevice}' -> '{newState.ActiveDevice}'");
                OnOutputDeviceChanged(new OutputDeviceChangedEventArgs
                {
                    PreviousDevice = oldState.ActiveDevice,
                    NewDevice = newState.ActiveDevice,
                    Timestamp = DateTime.Now
                });
            }

            // Volume change
            if (Math.Abs(oldState.Volume - newState.Volume) > 0.001f)
            {
                Logger.Debug($"Volume changed: {oldState.Volume:P0} -> {newState.Volume:P0}");
            }

            // DSP state changes
            if (oldState.DspEnabled != newState.DspEnabled ||
                oldState.EqualizerEnabled != newState.EqualizerEnabled ||
                oldState.CrossfadeEnabled != newState.CrossfadeEnabled ||
                oldState.ReplayGainMode != newState.ReplayGainMode)
            {
                Logger.Debug("DSP state changed");
            }

            // Generic state change event
            OnStateChanged(new AudioStateChangedEventArgs
            {
                PreviousState = oldState,
                NewState = newState,
                Timestamp = DateTime.Now
            });
        }

        /// <summary>
        /// Captures spectrum data from the currently playing track.
        /// </summary>
        /// <returns>Spectrum snapshot, or null if not available.</returns>
        public SpectrumSnapshot CaptureSpectrumData()
        {
            if (!_hasApiAccess || !_captureSpectrum) return null;

            try
            {
                if (_spectrumBuffer == null)
                    _spectrumBuffer = new float[_spectrumBinCount];

                if (_mbApi.NowPlaying_GetSpectrumData == null) return null;

                var validBins = _mbApi.NowPlaying_GetSpectrumData(_spectrumBuffer);

                if (validBins <= 0) return null;

                var snapshot = new SpectrumSnapshot
                {
                    Timestamp = DateTime.Now,
                    FFTData = (float[])_spectrumBuffer.Clone(),
                    ValidBinCount = validBins,
                    AverageLevel = CalculateAverageLevel(_spectrumBuffer, validBins),
                    PeakFrequency = CalculatePeakFrequency(_spectrumBuffer, validBins),
                    BandLevels = CalculateBandLevels(_spectrumBuffer, validBins)
                };

                AddToHistory(_spectrumHistory, snapshot);
                return snapshot;
            }
            catch (Exception ex)
            {
                Logger.Trace(ex, "Error capturing spectrum data");
                return null;
            }
        }

        /// <summary>
        /// Captures waveform data from the currently playing track.
        /// </summary>
        /// <returns>Waveform snapshot, or null if not available.</returns>
        public WaveformSnapshot CaptureWaveformData()
        {
            if (!_hasApiAccess || !_captureWaveform) return null;

            try
            {
                if (_waveformBuffer == null)
                    _waveformBuffer = new float[_waveformSampleCount];
                if (_peakBuffer == null)
                    _peakBuffer = new float[_waveformSampleCount];

                bool success = false;

                // Try extended version first (with peaks)
                if (_mbApi.NowPlaying_GetSoundGraphEx != null)
                {
                    success = _mbApi.NowPlaying_GetSoundGraphEx(_waveformBuffer, _peakBuffer);
                }

                // Fall back to basic version
                if (!success && _mbApi.NowPlaying_GetSoundGraph != null)
                {
                    success = _mbApi.NowPlaying_GetSoundGraph(_waveformBuffer);
                    // Clear peak buffer since we didn't get peak data
                    Array.Clear(_peakBuffer, 0, _peakBuffer.Length);
                }

                if (!success) return null;

                var maxAmp = _waveformBuffer.Max(v => Math.Abs(v));
                var rms = CalculateRMS(_waveformBuffer);

                var snapshot = new WaveformSnapshot
                {
                    Timestamp = DateTime.Now,
                    GraphData = (float[])_waveformBuffer.Clone(),
                    PeakData = (float[])_peakBuffer.Clone(),
                    MaxAmplitude = maxAmp,
                    RmsLevel = rms,
                    IsClipping = maxAmp >= 0.99f
                };

                AddToHistory(_waveformHistory, snapshot);
                return snapshot;
            }
            catch (Exception ex)
            {
                Logger.Trace(ex, "Error capturing waveform data");
                return null;
            }
        }

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

            try
            {
                // Refresh state from API
                if (_hasApiAccess)
                {
                    RefreshAllState();
                }

                var state = CurrentState;
                if (state == null) return;

                // Populate MetricsSnapshot with audio data
                snapshot.AudioOutputDevice = state.ActiveDevice ?? "Unknown";
                snapshot.SampleRate = state.SampleRate;
                snapshot.BitDepth = state.BitDepth;

                // Extended audio fields
                snapshot.AudioChannels = state.Channels;
                snapshot.AudioFormat = state.Format;
                snapshot.AudioBitrate = state.Bitrate;
                snapshot.Volume = state.Volume;
                snapshot.IsMuted = state.IsMuted;
                snapshot.DspEnabled = state.DspEnabled;
                snapshot.EqualizerEnabled = state.EqualizerEnabled;
                snapshot.CrossfadeEnabled = state.CrossfadeEnabled;
                snapshot.ReplayGainMode = state.ReplayGainMode;
                snapshot.AvailableDeviceCount = state.AvailableDevices?.Length ?? 0;
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Error collecting audio metrics");
            }
        }

        /// <summary>
        /// Gets a diagnostic summary of audio subsystem state.
        /// </summary>
        public string GetDiagnosticSummary()
        {
            var state = CurrentState;
            var deviceList = state?.AvailableDevices != null
                ? string.Join("\n  - ", state.AvailableDevices)
                : "(none)";

            return $@"Audio Subsystem Diagnostics
===========================
Output Device: {state?.ActiveDevice ?? "(unknown)"}
Sample Rate: {(state?.SampleRate > 0 ? $"{state.SampleRate}Hz" : "(unknown)")}
Channels: {(state?.Channels > 0 ? state.Channels.ToString() : "(unknown)")}
Format: {state?.Format ?? "(unknown)"}
Bitrate: {state?.Bitrate ?? "(unknown)"}

Volume: {state?.Volume:P0} {(state?.IsMuted == true ? "(MUTED)" : "")}

DSP State:
  Equalizer: {state?.EqualizerEnabled}
  DSP: {state?.DspEnabled}
  Crossfade: {state?.CrossfadeEnabled}
  ReplayGain: {state?.ReplayGainMode}

ReplayGain Values:
  Track: {state?.ReplayGainTrack ?? "(none)"}
  Album: {state?.ReplayGainAlbum ?? "(none)"}

Available Devices ({state?.AvailableDevices?.Length ?? 0}):
  - {deviceList}

API Access: {_hasApiAccess}
Initialized: {_initialized}
Spectrum Capture: {_captureSpectrum}
Waveform Capture: {_captureWaveform}";
        }

        /// <summary>
        /// Gets recent spectrum snapshots.
        /// </summary>
        public IReadOnlyList<SpectrumSnapshot> GetSpectrumHistory(int count = 60)
        {
            var all = _spectrumHistory.ToArray();
            return all.Skip(Math.Max(0, all.Length - count)).ToList();
        }

        /// <summary>
        /// Gets recent waveform snapshots.
        /// </summary>
        public IReadOnlyList<WaveformSnapshot> GetWaveformHistory(int count = 60)
        {
            var all = _waveformHistory.ToArray();
            return all.Skip(Math.Max(0, all.Length - count)).ToList();
        }

        #region Calculation Helpers

        private float CalculateAverageLevel(float[] data, int validCount)
        {
            if (data == null || validCount <= 0) return 0;
            float sum = 0;
            for (int i = 0; i < validCount && i < data.Length; i++)
            {
                sum += Math.Abs(data[i]);
            }
            return sum / validCount;
        }

        private float CalculatePeakFrequency(float[] data, int validCount)
        {
            if (data == null || validCount <= 0) return 0;
            int peakIndex = 0;
            float peakValue = 0;
            for (int i = 0; i < validCount && i < data.Length; i++)
            {
                if (data[i] > peakValue)
                {
                    peakValue = data[i];
                    peakIndex = i;
                }
            }
            // Approximate frequency (assuming 44100Hz sample rate, 512 bins)
            return peakIndex * (44100f / 2 / validCount);
        }

        private float[] CalculateBandLevels(float[] data, int validCount)
        {
            // Sub-bass, Bass, Mid, Treble
            var bands = new float[4];
            if (data == null || validCount <= 0) return bands;

            // Band boundaries (approximate for 512 bins at 44100Hz)
            int subBassMax = Math.Min(8, validCount);      // ~60Hz
            int bassMax = Math.Min(32, validCount);        // ~250Hz
            int midMax = Math.Min(128, validCount);        // ~2kHz
            // Treble = rest

            for (int i = 0; i < validCount && i < data.Length; i++)
            {
                if (i < subBassMax)
                    bands[0] += data[i];
                else if (i < bassMax)
                    bands[1] += data[i];
                else if (i < midMax)
                    bands[2] += data[i];
                else
                    bands[3] += data[i];
            }

            // Normalize
            if (subBassMax > 0) bands[0] /= subBassMax;
            if (bassMax > subBassMax) bands[1] /= (bassMax - subBassMax);
            if (midMax > bassMax) bands[2] /= (midMax - bassMax);
            if (validCount > midMax) bands[3] /= (validCount - midMax);

            return bands;
        }

        private float CalculateRMS(float[] data)
        {
            if (data == null || data.Length == 0) return 0;
            float sum = 0;
            foreach (var v in data)
            {
                sum += v * v;
            }
            return (float)Math.Sqrt(sum / data.Length);
        }

        private void AddToHistory<T>(ConcurrentQueue<T> queue, T item)
        {
            queue.Enqueue(item);
            while (queue.Count > MaxHistorySize)
            {
                queue.TryDequeue(out _);
            }
        }

        #endregion

        #region Events

        protected virtual void OnStateChanged(AudioStateChangedEventArgs e)
            => StateChanged?.Invoke(this, e);

        protected virtual void OnOutputDeviceChanged(OutputDeviceChangedEventArgs e)
            => OutputDeviceChanged?.Invoke(this, e);

        #endregion

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

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

    #region Data Models

    /// <summary>
    /// Complete audio state snapshot.
    /// </summary>
    public class AudioState
    {
        // Output Device
        public string ActiveDevice { get; set; }
        public string[] AvailableDevices { get; set; }
        public DateTime DeviceEnumerationTime { get; set; }

        // Current Track Audio Properties
        public int SampleRate { get; set; }
        public int BitDepth { get; set; }
        public int Channels { get; set; }
        public string Format { get; set; }
        public string Bitrate { get; set; }

        // Player States
        public float Volume { get; set; }
        public bool IsMuted { get; set; }

        // DSP State
        public bool DspEnabled { get; set; }
        public bool EqualizerEnabled { get; set; }
        public bool CrossfadeEnabled { get; set; }
        public Plugin.ReplayGainMode ReplayGainMode { get; set; }

        // ReplayGain Values (from file properties)
        public string ReplayGainTrack { get; set; }
        public string ReplayGainAlbum { get; set; }

        public DateTime Timestamp { get; set; }
    }

    /// <summary>
    /// Spectrum analysis snapshot.
    /// </summary>
    public class SpectrumSnapshot
    {
        public DateTime Timestamp { get; set; }
        public float[] FFTData { get; set; }
        public int ValidBinCount { get; set; }
        public float PeakFrequency { get; set; }
        public float AverageLevel { get; set; }
        public float[] BandLevels { get; set; }  // Sub-bass, Bass, Mid, Treble
    }

    /// <summary>
    /// Waveform analysis snapshot.
    /// </summary>
    public class WaveformSnapshot
    {
        public DateTime Timestamp { get; set; }
        public float[] GraphData { get; set; }
        public float[] PeakData { get; set; }
        public float MaxAmplitude { get; set; }
        public float RmsLevel { get; set; }
        public bool IsClipping { get; set; }
    }

    /// <summary>
    /// Event args for audio state changes.
    /// </summary>
    public class AudioStateChangedEventArgs : EventArgs
    {
        public AudioState PreviousState { get; set; }
        public AudioState NewState { get; set; }
        public DateTime Timestamp { get; set; }
    }

    /// <summary>
    /// Event args for output device changes.
    /// </summary>
    public class OutputDeviceChangedEventArgs : EventArgs
    {
        public string PreviousDevice { get; set; }
        public string NewDevice { get; set; }
        public DateTime Timestamp { get; set; }
    }

    #endregion
}
