using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using NLog;

namespace MusicBeePlugin.Clouseau.Introspection
{
    /// <summary>
    /// Discovers and inspects MusicBee plugins in the Plugins folder.
    /// Uses reflection to identify valid plugins and extract their metadata.
    /// </summary>
    public class PluginDiscovery
    {
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
        private readonly string _pluginsPath;

        /// <summary>
        /// Creates a new PluginDiscovery instance with the specified plugins path.
        /// </summary>
        /// <param name="pluginsPath">Path to the MusicBee Plugins folder.</param>
        public PluginDiscovery(string pluginsPath)
        {
            _pluginsPath = pluginsPath ?? throw new ArgumentNullException(nameof(pluginsPath));
        }

        /// <summary>
        /// Creates a PluginDiscovery instance using the MusicBee application path.
        /// </summary>
        /// <param name="musicBeeAppPath">Path returned by MB_GetApplicationPath().</param>
        /// <returns>A new PluginDiscovery instance.</returns>
        public static PluginDiscovery FromMusicBeePath(string musicBeeAppPath)
        {
            if (string.IsNullOrEmpty(musicBeeAppPath))
            {
                throw new ArgumentNullException(nameof(musicBeeAppPath));
            }

            var pluginsPath = Path.Combine(musicBeeAppPath, "Plugins");
            return new PluginDiscovery(pluginsPath);
        }

        /// <summary>
        /// Gets the path to the Plugins folder.
        /// </summary>
        public string PluginsPath => _pluginsPath;

        /// <summary>
        /// Discovers all plugins in the MusicBee Plugins folder.
        /// </summary>
        /// <returns>List of discovered plugins with their metadata.</returns>
        public List<DiscoveredPlugin> DiscoverPlugins()
        {
            var plugins = new List<DiscoveredPlugin>();

            if (!Directory.Exists(_pluginsPath))
            {
                Logger.Warn($"Plugins directory does not exist: {_pluginsPath}");
                return plugins;
            }

            Logger.Info($"Scanning for plugins in: {_pluginsPath}");

            // Find all DLL files in the Plugins folder
            var dllFiles = Directory.GetFiles(_pluginsPath, "*.dll", SearchOption.TopDirectoryOnly);

            foreach (var dllPath in dllFiles)
            {
                var plugin = InspectAssembly(dllPath);
                plugins.Add(plugin);

                if (plugin.IsValidPlugin)
                {
                    Logger.Debug($"Found valid plugin: {plugin.PluginName ?? plugin.FileName}");
                }
                else
                {
                    Logger.Trace($"Skipped non-plugin: {plugin.FileName} - {plugin.LoadError}");
                }
            }

            Logger.Info($"Discovered {plugins.Count(p => p.IsValidPlugin)} valid plugins out of {plugins.Count} DLLs");

            return plugins;
        }

        /// <summary>
        /// Inspects a single assembly to determine if it's a valid MusicBee plugin.
        /// </summary>
        /// <param name="dllPath">Path to the DLL file.</param>
        /// <returns>DiscoveredPlugin with the inspection results.</returns>
        public DiscoveredPlugin InspectAssembly(string dllPath)
        {
            var plugin = new DiscoveredPlugin
            {
                FilePath = dllPath,
                FileName = Path.GetFileName(dllPath),
                IsValidPlugin = false
            };

            try
            {
                // Get file info
                var fileInfo = new FileInfo(dllPath);
                plugin.FileSizeBytes = fileInfo.Length;
                plugin.LastModified = fileInfo.LastWriteTime;

                // Load assembly for reflection (in reflection-only context if possible)
                Assembly assembly;
                try
                {
                    // Use LoadFrom to avoid locking issues and enable inspection
                    assembly = Assembly.LoadFrom(dllPath);
                }
                catch (BadImageFormatException)
                {
                    plugin.LoadError = "Not a .NET assembly";
                    return plugin;
                }
                catch (FileLoadException ex)
                {
                    plugin.LoadError = $"File load error: {ex.Message}";
                    return plugin;
                }

                // Get assembly info
                var assemblyName = assembly.GetName();
                plugin.AssemblyName = assemblyName.Name;
                plugin.Version = assemblyName.Version?.ToString() ?? "Unknown";

                // Try to get file version
                try
                {
                    var fileVersionInfo = System.Diagnostics.FileVersionInfo.GetVersionInfo(dllPath);
                    plugin.FileVersion = fileVersionInfo.FileVersion ?? plugin.Version;
                }
                catch
                {
                    plugin.FileVersion = plugin.Version;
                }

                // Look for MusicBee plugin pattern
                var pluginType = FindPluginType(assembly);
                if (pluginType == null)
                {
                    plugin.LoadError = "No MusicBee plugin class found";
                    return plugin;
                }

                plugin.PluginTypeName = pluginType.FullName;
                plugin.IsValidPlugin = true;

                // Try to extract plugin info
                ExtractPluginInfo(pluginType, plugin);
            }
            catch (ReflectionTypeLoadException ex)
            {
                plugin.LoadError = $"Type load error: {string.Join("; ", ex.LoaderExceptions.Take(3).Select(e => e?.Message ?? "Unknown"))}";
            }
            catch (Exception ex)
            {
                plugin.LoadError = $"Inspection error: {ex.Message}";
                Logger.Error(ex, $"Error inspecting assembly: {dllPath}");
            }

            return plugin;
        }

        /// <summary>
        /// Finds the MusicBee plugin type in an assembly.
        /// Looks for a class with an Initialise method matching the MusicBee pattern.
        /// </summary>
        private Type FindPluginType(Assembly assembly)
        {
            try
            {
                foreach (var type in assembly.GetExportedTypes())
                {
                    // Check for MusicBee plugin pattern:
                    // 1. Has Initialise method with IntPtr parameter returning PluginInfo
                    // 2. Usually in MusicBeePlugin namespace
                    // 3. Class is typically named "Plugin"

                    var initialiseMethod = type.GetMethod("Initialise",
                        BindingFlags.Public | BindingFlags.Instance,
                        null,
                        new[] { typeof(IntPtr) },
                        null);

                    if (initialiseMethod != null)
                    {
                        // Check if return type looks like PluginInfo
                        var returnType = initialiseMethod.ReturnType;
                        if (returnType.Name == "PluginInfo" || returnType.Name == "MusicBeePlugin+PluginInfo")
                        {
                            return type;
                        }

                        // Also check for the pattern where it returns the nested PluginInfo class
                        if (returnType.IsClass && returnType.GetField("Name") != null)
                        {
                            return type;
                        }
                    }

                    // Alternative check: Look for nested PluginInfo class
                    var nestedPluginInfo = type.GetNestedType("PluginInfo");
                    if (nestedPluginInfo != null && initialiseMethod != null)
                    {
                        return type;
                    }
                }
            }
            catch (ReflectionTypeLoadException)
            {
                // Some types couldn't be loaded, but we may have found what we need
            }

            return null;
        }

        /// <summary>
        /// Extracts plugin information from static fields or by attempting to read PluginInfo.
        /// </summary>
        private void ExtractPluginInfo(Type pluginType, DiscoveredPlugin plugin)
        {
            try
            {
                // Look for common static fields that might contain plugin info
                // Many plugins have static fields like _about, pluginInfo, etc.

                // Try to find PluginInfo fields
                var infoFields = pluginType.GetFields(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)
                    .Where(f => f.FieldType.Name.Contains("PluginInfo"))
                    .ToList();

                // Also check for nested PluginInfo type
                var nestedTypes = pluginType.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic);
                var pluginInfoType = nestedTypes.FirstOrDefault(t => t.Name == "PluginInfo");

                if (pluginInfoType != null)
                {
                    // Found PluginInfo type, try to extract default values
                    ExtractFromPluginInfoType(pluginInfoType, plugin);
                }

                // Check assembly attributes for additional info
                ExtractFromAssemblyAttributes(pluginType.Assembly, plugin);

                // Default plugin type if not set
                if (string.IsNullOrEmpty(plugin.PluginType))
                {
                    plugin.PluginType = "General";
                }
            }
            catch (Exception ex)
            {
                Logger.Trace($"Could not extract plugin info from {pluginType.FullName}: {ex.Message}");
            }
        }

        /// <summary>
        /// Attempts to extract plugin info from the PluginInfo type definition.
        /// </summary>
        private void ExtractFromPluginInfoType(Type pluginInfoType, DiscoveredPlugin plugin)
        {
            try
            {
                // Get field info for common fields
                var nameField = pluginInfoType.GetField("Name");
                var descField = pluginInfoType.GetField("Description");
                var authorField = pluginInfoType.GetField("Author");
                var typeField = pluginInfoType.GetField("Type");
                var majorField = pluginInfoType.GetField("VersionMajor");
                var minorField = pluginInfoType.GetField("VersionMinor");
                var revField = pluginInfoType.GetField("Revision");

                // We can't get default values directly, but we can note the structure exists
                // The actual values would need to be read from an instantiated object

                // For now, try to use assembly attributes as fallback
            }
            catch
            {
                // Ignore extraction errors
            }
        }

        /// <summary>
        /// Extracts plugin metadata from assembly attributes.
        /// </summary>
        private void ExtractFromAssemblyAttributes(Assembly assembly, DiscoveredPlugin plugin)
        {
            try
            {
                // Title
                var titleAttr = assembly.GetCustomAttribute<AssemblyTitleAttribute>();
                if (titleAttr != null && !string.IsNullOrEmpty(titleAttr.Title))
                {
                    plugin.PluginName = titleAttr.Title;
                }
                else
                {
                    plugin.PluginName = plugin.AssemblyName;
                }

                // Description
                var descAttr = assembly.GetCustomAttribute<AssemblyDescriptionAttribute>();
                if (descAttr != null && !string.IsNullOrEmpty(descAttr.Description))
                {
                    plugin.PluginDescription = descAttr.Description;
                }

                // Company/Author
                var companyAttr = assembly.GetCustomAttribute<AssemblyCompanyAttribute>();
                if (companyAttr != null && !string.IsNullOrEmpty(companyAttr.Company))
                {
                    plugin.PluginAuthor = companyAttr.Company;
                }

                // Version
                var versionAttr = assembly.GetCustomAttribute<AssemblyFileVersionAttribute>();
                if (versionAttr != null && !string.IsNullOrEmpty(versionAttr.Version))
                {
                    var parts = versionAttr.Version.Split('.');
                    if (parts.Length >= 1 && short.TryParse(parts[0], out var major))
                        plugin.VersionMajor = major;
                    if (parts.Length >= 2 && short.TryParse(parts[1], out var minor))
                        plugin.VersionMinor = minor;
                    if (parts.Length >= 3 && short.TryParse(parts[2], out var rev))
                        plugin.Revision = rev;
                }
            }
            catch (Exception ex)
            {
                Logger.Trace($"Could not extract assembly attributes: {ex.Message}");
            }
        }

        /// <summary>
        /// Gets only valid MusicBee plugins from the discovered list.
        /// </summary>
        public List<DiscoveredPlugin> DiscoverValidPlugins()
        {
            return DiscoverPlugins().Where(p => p.IsValidPlugin).ToList();
        }

        /// <summary>
        /// Gets a summary of all discovered plugins.
        /// </summary>
        public string GetDiscoverySummary()
        {
            var plugins = DiscoverPlugins();
            var valid = plugins.Where(p => p.IsValidPlugin).ToList();
            var invalid = plugins.Where(p => !p.IsValidPlugin).ToList();

            var summary = new System.Text.StringBuilder();
            summary.AppendLine($"=== MusicBee Plugin Discovery ===");
            summary.AppendLine($"Plugins Path: {_pluginsPath}");
            summary.AppendLine($"Total DLLs: {plugins.Count}");
            summary.AppendLine($"Valid Plugins: {valid.Count}");
            summary.AppendLine($"Non-Plugins: {invalid.Count}");
            summary.AppendLine();

            if (valid.Any())
            {
                summary.AppendLine("--- Valid Plugins ---");
                foreach (var plugin in valid.OrderBy(p => p.PluginName ?? p.FileName))
                {
                    summary.AppendLine(plugin.ToDisplayString());
                    summary.AppendLine();
                }
            }

            if (invalid.Any())
            {
                summary.AppendLine("--- Non-Plugin DLLs ---");
                foreach (var plugin in invalid.OrderBy(p => p.FileName))
                {
                    summary.AppendLine($"  {plugin.FileName}: {plugin.LoadError}");
                }
            }

            return summary.ToString();
        }
    }
}
