using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using NLog;

namespace MusicBeePlugin.Clouseau.Introspection
{
    /// <summary>
    /// Captures deep state snapshots of the MusicBee environment.
    /// Includes assemblies, threads, player state, environment info, and plugins.
    /// </summary>
    public class StateDumper
    {
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
        private readonly Plugin.MusicBeeApiInterface _mbApi;
        private readonly string _dataFolder;
        private readonly PluginDiscovery _pluginDiscovery;

        /// <summary>
        /// Creates a new StateDumper instance.
        /// </summary>
        /// <param name="mbApi">MusicBee API interface.</param>
        /// <param name="dataFolder">Folder to save dump files.</param>
        /// <param name="pluginsPath">Path to MusicBee Plugins folder.</param>
        public StateDumper(Plugin.MusicBeeApiInterface mbApi, string dataFolder, string pluginsPath)
        {
            _mbApi = mbApi;
            _dataFolder = dataFolder ?? throw new ArgumentNullException(nameof(dataFolder));
            _pluginDiscovery = new PluginDiscovery(pluginsPath);

            // Ensure data folder exists
            if (!Directory.Exists(_dataFolder))
            {
                Directory.CreateDirectory(_dataFolder);
            }
        }

        /// <summary>
        /// Captures a complete state dump and saves it to a JSON file.
        /// </summary>
        /// <returns>Path to the dump file.</returns>
        public string DumpState()
        {
            Logger.Info("Starting state dump...");
            var stopwatch = Stopwatch.StartNew();

            var dump = new StateDump
            {
                Timestamp = DateTime.Now,
                DumpId = Guid.NewGuid().ToString("N").Substring(0, 8)
            };

            try
            {
                // Capture all state components
                dump.Environment = CaptureEnvironmentInfo();
                dump.MusicBeeState = CaptureMusicBeeState();
                dump.LoadedAssemblies = CaptureLoadedAssemblies();
                dump.ThreadInfo = CaptureThreadInfo();
                dump.DiscoveredPlugins = CaptureDiscoveredPlugins();
                dump.MemoryInfo = CaptureMemoryInfo();
                dump.ProcessInfo = CaptureProcessInfo();

                stopwatch.Stop();
                dump.DumpDurationMs = stopwatch.ElapsedMilliseconds;
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Error during state dump");
                dump.Errors.Add($"Dump error: {ex.Message}");
            }

            // Save to file
            var fileName = $"state_dump_{dump.Timestamp:yyyyMMdd_HHmmss}_{dump.DumpId}.json";
            var filePath = Path.Combine(_dataFolder, fileName);

            try
            {
                var json = JsonConvert.SerializeObject(dump, Formatting.Indented, new JsonSerializerSettings
                {
                    Converters = { new StringEnumConverter() },
                    NullValueHandling = NullValueHandling.Ignore
                });
                File.WriteAllText(filePath, json);
                Logger.Info($"State dump saved to: {filePath} ({stopwatch.ElapsedMilliseconds}ms)");
            }
            catch (Exception ex)
            {
                Logger.Error(ex, $"Failed to save state dump to: {filePath}");
                throw;
            }

            return filePath;
        }

        /// <summary>
        /// Captures environment information.
        /// </summary>
        private EnvironmentInfo CaptureEnvironmentInfo()
        {
            return new EnvironmentInfo
            {
                MachineName = Environment.MachineName,
                OSVersion = Environment.OSVersion.ToString(),
                Is64BitOS = Environment.Is64BitOperatingSystem,
                Is64BitProcess = Environment.Is64BitProcess,
                ProcessorCount = Environment.ProcessorCount,
                CLRVersion = Environment.Version.ToString(),
                WorkingDirectory = Environment.CurrentDirectory,
                SystemDirectory = Environment.SystemDirectory,
                UserName = Environment.UserName,
                UserDomainName = Environment.UserDomainName,
                CommandLine = Environment.CommandLine,
                FrameworkDescription = System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription
            };
        }

        /// <summary>
        /// Captures current MusicBee player state.
        /// </summary>
        private MusicBeeStateInfo CaptureMusicBeeState()
        {
            var state = new MusicBeeStateInfo();

            try
            {
                // API Version
                state.ApiRevision = _mbApi.ApiRevision;
                state.MusicBeeVersion = _mbApi.MusicBeeVersion.ToString();

                // Player state
                state.PlayState = _mbApi.Player_GetPlayState().ToString();
                state.Volume = _mbApi.Player_GetVolume();
                state.IsMuted = _mbApi.Player_GetMute();
                state.Position = _mbApi.Player_GetPosition();
                state.IsShuffle = _mbApi.Player_GetShuffle();
                state.RepeatMode = _mbApi.Player_GetRepeat().ToString();
                state.IsAutoDjEnabled = _mbApi.Player_GetAutoDjEnabled();
                state.IsStopAfterCurrent = _mbApi.Player_GetStopAfterCurrentEnabled();
                state.IsEqualiserEnabled = _mbApi.Player_GetEqualiserEnabled();
                state.IsDspEnabled = _mbApi.Player_GetDspEnabled();
                state.IsScrobbleEnabled = _mbApi.Player_GetScrobbleEnabled();
                state.ReplayGainMode = _mbApi.Player_GetReplayGainMode().ToString();

                // Current track
                var fileUrl = _mbApi.NowPlaying_GetFileUrl();
                if (!string.IsNullOrEmpty(fileUrl))
                {
                    state.CurrentTrack = new TrackInfo
                    {
                        FileUrl = fileUrl,
                        Duration = _mbApi.NowPlaying_GetDuration(),
                        Title = _mbApi.NowPlaying_GetFileTag(Plugin.MetaDataType.TrackTitle),
                        Artist = _mbApi.NowPlaying_GetFileTag(Plugin.MetaDataType.Artist),
                        Album = _mbApi.NowPlaying_GetFileTag(Plugin.MetaDataType.Album),
                        AlbumArtist = _mbApi.NowPlaying_GetFileTag(Plugin.MetaDataType.AlbumArtist),
                        Genre = _mbApi.NowPlaying_GetFileTag(Plugin.MetaDataType.Genre),
                        Year = _mbApi.NowPlaying_GetFileTag(Plugin.MetaDataType.Year),
                        TrackNo = _mbApi.NowPlaying_GetFileTag(Plugin.MetaDataType.TrackNo),
                        Rating = _mbApi.NowPlaying_GetFileTag(Plugin.MetaDataType.Rating),
                        Bitrate = _mbApi.NowPlaying_GetFileProperty(Plugin.FilePropertyType.Bitrate),
                        Format = _mbApi.NowPlaying_GetFileProperty(Plugin.FilePropertyType.Format),
                        SampleRate = _mbApi.NowPlaying_GetFileProperty(Plugin.FilePropertyType.SampleRate),
                        Channels = _mbApi.NowPlaying_GetFileProperty(Plugin.FilePropertyType.Channels)
                    };
                }

                // Queue info
                state.NowPlayingIndex = _mbApi.NowPlayingList_GetCurrentIndex();
                state.HasPriorTracks = _mbApi.NowPlayingList_IsAnyPriorTracks();
                state.HasFollowingTracks = _mbApi.NowPlayingList_IsAnyFollowingTracks();

                // Output devices
                if (_mbApi.Player_GetOutputDevices(out var deviceNames, out var activeDevice))
                {
                    state.OutputDevices = deviceNames?.ToList() ?? new List<string>();
                    state.ActiveOutputDevice = activeDevice;
                }

                // Paths
                state.PersistentStoragePath = _mbApi.Setting_GetPersistentStoragePath();
                state.CurrentSkin = _mbApi.Setting_GetSkin();
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Error capturing MusicBee state");
                state.CaptureError = ex.Message;
            }

            return state;
        }

        /// <summary>
        /// Captures information about all loaded assemblies.
        /// </summary>
        private List<AssemblyInfo> CaptureLoadedAssemblies()
        {
            var assemblies = new List<AssemblyInfo>();

            try
            {
                foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
                {
                    try
                    {
                        var name = assembly.GetName();
                        assemblies.Add(new AssemblyInfo
                        {
                            Name = name.Name,
                            Version = name.Version?.ToString(),
                            FullName = assembly.FullName,
                            Location = assembly.IsDynamic ? "[Dynamic]" : assembly.Location,
                            IsGAC = assembly.GlobalAssemblyCache,
                            IsDynamic = assembly.IsDynamic
                        });
                    }
                    catch
                    {
                        // Skip assemblies we can't inspect
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Error capturing loaded assemblies");
            }

            return assemblies.OrderBy(a => a.Name).ToList();
        }

        /// <summary>
        /// Captures thread information.
        /// </summary>
        private ThreadStateInfo CaptureThreadInfo()
        {
            var info = new ThreadStateInfo();

            try
            {
                var process = Process.GetCurrentProcess();
                info.TotalThreads = process.Threads.Count;
                info.ThreadDetails = new List<ThreadDetail>();

                foreach (ProcessThread thread in process.Threads)
                {
                    try
                    {
                        info.ThreadDetails.Add(new ThreadDetail
                        {
                            Id = thread.Id,
                            State = thread.ThreadState.ToString(),
                            Priority = thread.BasePriority,
                            TotalProcessorTime = thread.TotalProcessorTime.TotalMilliseconds,
                            UserProcessorTime = thread.UserProcessorTime.TotalMilliseconds,
                            StartTime = thread.StartTime
                        });
                    }
                    catch
                    {
                        // Some thread properties may not be accessible
                    }
                }

                // Managed thread info
                info.CurrentManagedThreadId = Thread.CurrentThread.ManagedThreadId;
                info.IsThreadPoolThread = Thread.CurrentThread.IsThreadPoolThread;

                // Thread pool stats
                ThreadPool.GetAvailableThreads(out var workerThreads, out var completionThreads);
                ThreadPool.GetMaxThreads(out var maxWorker, out var maxCompletion);
                info.ThreadPoolWorkerAvailable = workerThreads;
                info.ThreadPoolCompletionAvailable = completionThreads;
                info.ThreadPoolWorkerMax = maxWorker;
                info.ThreadPoolCompletionMax = maxCompletion;
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Error capturing thread info");
            }

            return info;
        }

        /// <summary>
        /// Captures discovered plugins.
        /// </summary>
        private List<DiscoveredPlugin> CaptureDiscoveredPlugins()
        {
            try
            {
                return _pluginDiscovery.DiscoverPlugins();
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Error discovering plugins");
                return new List<DiscoveredPlugin>();
            }
        }

        /// <summary>
        /// Captures memory information.
        /// </summary>
        private MemoryStateInfo CaptureMemoryInfo()
        {
            var info = new MemoryStateInfo();

            try
            {
                var process = Process.GetCurrentProcess();

                info.WorkingSet = process.WorkingSet64;
                info.PrivateMemory = process.PrivateMemorySize64;
                info.VirtualMemory = process.VirtualMemorySize64;
                info.PagedMemory = process.PagedMemorySize64;
                info.PagedSystemMemory = process.PagedSystemMemorySize64;
                info.NonPagedSystemMemory = process.NonpagedSystemMemorySize64;
                info.PeakWorkingSet = process.PeakWorkingSet64;
                info.PeakVirtualMemory = process.PeakVirtualMemorySize64;
                info.PeakPagedMemory = process.PeakPagedMemorySize64;

                // GC Info
                info.GCTotalMemory = GC.GetTotalMemory(false);
                info.GCGen0Collections = GC.CollectionCount(0);
                info.GCGen1Collections = GC.CollectionCount(1);
                info.GCGen2Collections = GC.CollectionCount(2);
                info.GCMaxGeneration = GC.MaxGeneration;
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Error capturing memory info");
            }

            return info;
        }

        /// <summary>
        /// Captures process information.
        /// </summary>
        private ProcessStateInfo CaptureProcessInfo()
        {
            var info = new ProcessStateInfo();

            try
            {
                var process = Process.GetCurrentProcess();

                info.ProcessId = process.Id;
                info.ProcessName = process.ProcessName;
                info.StartTime = process.StartTime;
                info.TotalProcessorTime = process.TotalProcessorTime;
                info.UserProcessorTime = process.UserProcessorTime;
                info.PrivilegedProcessorTime = process.PrivilegedProcessorTime;
                info.HandleCount = process.HandleCount;
                info.BasePriority = process.BasePriority;
                info.PriorityClass = process.PriorityClass.ToString();
                info.MainModulePath = process.MainModule?.FileName;
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Error capturing process info");
            }

            return info;
        }

        /// <summary>
        /// Gets a list of existing dump files.
        /// </summary>
        public List<DumpFileInfo> GetExistingDumps()
        {
            var dumps = new List<DumpFileInfo>();

            try
            {
                if (Directory.Exists(_dataFolder))
                {
                    var files = Directory.GetFiles(_dataFolder, "state_dump_*.json");
                    foreach (var file in files)
                    {
                        var fileInfo = new FileInfo(file);
                        dumps.Add(new DumpFileInfo
                        {
                            FilePath = file,
                            FileName = fileInfo.Name,
                            CreatedTime = fileInfo.CreationTime,
                            FileSizeBytes = fileInfo.Length
                        });
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Error listing dump files");
            }

            return dumps.OrderByDescending(d => d.CreatedTime).ToList();
        }
    }

    #region Data Models

    /// <summary>
    /// Complete state dump container.
    /// </summary>
    public class StateDump
    {
        public DateTime Timestamp { get; set; }
        public string DumpId { get; set; }
        public long DumpDurationMs { get; set; }
        public List<string> Errors { get; set; } = new List<string>();
        public EnvironmentInfo Environment { get; set; }
        public MusicBeeStateInfo MusicBeeState { get; set; }
        public List<AssemblyInfo> LoadedAssemblies { get; set; }
        public ThreadStateInfo ThreadInfo { get; set; }
        public List<DiscoveredPlugin> DiscoveredPlugins { get; set; }
        public MemoryStateInfo MemoryInfo { get; set; }
        public ProcessStateInfo ProcessInfo { get; set; }
    }

    public class EnvironmentInfo
    {
        public string MachineName { get; set; }
        public string OSVersion { get; set; }
        public bool Is64BitOS { get; set; }
        public bool Is64BitProcess { get; set; }
        public int ProcessorCount { get; set; }
        public string CLRVersion { get; set; }
        public string FrameworkDescription { get; set; }
        public string WorkingDirectory { get; set; }
        public string SystemDirectory { get; set; }
        public string UserName { get; set; }
        public string UserDomainName { get; set; }
        public string CommandLine { get; set; }
    }

    public class MusicBeeStateInfo
    {
        public short ApiRevision { get; set; }
        public string MusicBeeVersion { get; set; }
        public string PlayState { get; set; }
        public float Volume { get; set; }
        public bool IsMuted { get; set; }
        public int Position { get; set; }
        public bool IsShuffle { get; set; }
        public string RepeatMode { get; set; }
        public bool IsAutoDjEnabled { get; set; }
        public bool IsStopAfterCurrent { get; set; }
        public bool IsEqualiserEnabled { get; set; }
        public bool IsDspEnabled { get; set; }
        public bool IsScrobbleEnabled { get; set; }
        public string ReplayGainMode { get; set; }
        public TrackInfo CurrentTrack { get; set; }
        public int NowPlayingIndex { get; set; }
        public bool HasPriorTracks { get; set; }
        public bool HasFollowingTracks { get; set; }
        public List<string> OutputDevices { get; set; }
        public string ActiveOutputDevice { get; set; }
        public string PersistentStoragePath { get; set; }
        public string CurrentSkin { get; set; }
        public string CaptureError { get; set; }
    }

    public class TrackInfo
    {
        public string FileUrl { get; set; }
        public int Duration { get; set; }
        public string Title { get; set; }
        public string Artist { get; set; }
        public string Album { get; set; }
        public string AlbumArtist { get; set; }
        public string Genre { get; set; }
        public string Year { get; set; }
        public string TrackNo { get; set; }
        public string Rating { get; set; }
        public string Bitrate { get; set; }
        public string Format { get; set; }
        public string SampleRate { get; set; }
        public string Channels { get; set; }
    }

    public class AssemblyInfo
    {
        public string Name { get; set; }
        public string Version { get; set; }
        public string FullName { get; set; }
        public string Location { get; set; }
        public bool IsGAC { get; set; }
        public bool IsDynamic { get; set; }
    }

    public class ThreadStateInfo
    {
        public int TotalThreads { get; set; }
        public int CurrentManagedThreadId { get; set; }
        public bool IsThreadPoolThread { get; set; }
        public int ThreadPoolWorkerAvailable { get; set; }
        public int ThreadPoolCompletionAvailable { get; set; }
        public int ThreadPoolWorkerMax { get; set; }
        public int ThreadPoolCompletionMax { get; set; }
        public List<ThreadDetail> ThreadDetails { get; set; }
    }

    public class ThreadDetail
    {
        public int Id { get; set; }
        public string State { get; set; }
        public int Priority { get; set; }
        public double TotalProcessorTime { get; set; }
        public double UserProcessorTime { get; set; }
        public DateTime StartTime { get; set; }
    }

    public class MemoryStateInfo
    {
        public long WorkingSet { get; set; }
        public long PrivateMemory { get; set; }
        public long VirtualMemory { get; set; }
        public long PagedMemory { get; set; }
        public long PagedSystemMemory { get; set; }
        public long NonPagedSystemMemory { get; set; }
        public long PeakWorkingSet { get; set; }
        public long PeakVirtualMemory { get; set; }
        public long PeakPagedMemory { get; set; }
        public long GCTotalMemory { get; set; }
        public int GCGen0Collections { get; set; }
        public int GCGen1Collections { get; set; }
        public int GCGen2Collections { get; set; }
        public int GCMaxGeneration { get; set; }
    }

    public class ProcessStateInfo
    {
        public int ProcessId { get; set; }
        public string ProcessName { get; set; }
        public DateTime StartTime { get; set; }
        public TimeSpan TotalProcessorTime { get; set; }
        public TimeSpan UserProcessorTime { get; set; }
        public TimeSpan PrivilegedProcessorTime { get; set; }
        public int HandleCount { get; set; }
        public int BasePriority { get; set; }
        public string PriorityClass { get; set; }
        public string MainModulePath { get; set; }
    }

    public class DumpFileInfo
    {
        public string FilePath { get; set; }
        public string FileName { get; set; }
        public DateTime CreatedTime { get; set; }
        public long FileSizeBytes { get; set; }
    }

    #endregion
}
