using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using System.ComponentModel;

namespace MusicBeePlugin.Clouseau.Introspection
{
    /// <summary>
    /// Core reflection-based introspection engine for MusicBee.
    /// Implements the three pillars: Assembly Explorer, UI Tree Inspector, Event/Method Spy.
    /// </summary>
    public class ReflectionExplorer
    {
        private static readonly BindingFlags AllMembers =
            BindingFlags.Public | BindingFlags.NonPublic |
            BindingFlags.Instance | BindingFlags.Static;

        private Form _mainForm;
        private Assembly _musicBeeAssembly;

        /// <summary>
        /// Gets all assemblies loaded in the current AppDomain.
        /// </summary>
        public IEnumerable<LoadedAssemblyInfo> GetLoadedAssemblies()
        {
            return AppDomain.CurrentDomain.GetAssemblies()
                .OrderBy(a => a.GetName().Name)
                .Select(a => new LoadedAssemblyInfo(a));
        }

        /// <summary>
        /// Gets assemblies filtered by category (MusicBee, Plugins, System, etc.)
        /// </summary>
        public IEnumerable<LoadedAssemblyInfo> GetAssembliesByCategory(string category)
        {
            var assemblies = GetLoadedAssemblies();

            return category?.ToLower() switch
            {
                "musicbee" => assemblies.Where(a =>
                    a.Name.IndexOf("MusicBee", StringComparison.OrdinalIgnoreCase) >= 0),
                "plugins" => assemblies.Where(a =>
                    a.Name.StartsWith("mb_", StringComparison.OrdinalIgnoreCase) ||
                    a.Name.IndexOf("Plugin", StringComparison.OrdinalIgnoreCase) >= 0),
                "system" => assemblies.Where(a =>
                    a.Name.StartsWith("System", StringComparison.OrdinalIgnoreCase) ||
                    a.Name.StartsWith("Microsoft", StringComparison.OrdinalIgnoreCase) ||
                    a.Name.StartsWith("mscorlib", StringComparison.OrdinalIgnoreCase)),
                "ui" => assemblies.Where(a =>
                    a.Name.IndexOf("Forms", StringComparison.OrdinalIgnoreCase) >= 0 ||
                    a.Name.IndexOf("Drawing", StringComparison.OrdinalIgnoreCase) >= 0 ||
                    a.Name.IndexOf("Presentation", StringComparison.OrdinalIgnoreCase) >= 0),
                _ => assemblies
            };
        }

        /// <summary>
        /// Finds and caches MusicBee's main form.
        /// </summary>
        public Form FindMainForm()
        {
            if (_mainForm != null && !_mainForm.IsDisposed)
                return _mainForm;

            _mainForm = Application.OpenForms
                .Cast<Form>()
                .FirstOrDefault(f =>
                    f.GetType().Name.Contains("MusicBee") ||
                    f.GetType().Assembly.GetName().Name.Contains("MusicBee") ||
                    f.Text.Contains("MusicBee"));

            // Fallback: find the form with the most controls (likely main form)
            if (_mainForm == null)
            {
                _mainForm = Application.OpenForms
                    .Cast<Form>()
                    .OrderByDescending(f => CountControls(f))
                    .FirstOrDefault();
            }

            if (_mainForm != null)
            {
                _musicBeeAssembly = _mainForm.GetType().Assembly;
            }

            return _mainForm;
        }

        /// <summary>
        /// Gets all open forms in the application.
        /// </summary>
        public IEnumerable<FormInfo> GetOpenForms()
        {
            return Application.OpenForms
                .Cast<Form>()
                .Select(f => new FormInfo(f));
        }

        /// <summary>
        /// Builds the complete UI control tree starting from the main form.
        /// </summary>
        public ControlNode BuildControlTree()
        {
            var mainForm = FindMainForm();
            if (mainForm == null)
                return null;

            return BuildControlNode(mainForm, 0);
        }

        /// <summary>
        /// Builds a control tree starting from any control.
        /// </summary>
        public ControlNode BuildControlTree(Control root)
        {
            return BuildControlNode(root, 0);
        }

        private ControlNode BuildControlNode(Control control, int depth)
        {
            var node = new ControlNode
            {
                Control = control,
                Type = control.GetType().FullName,
                Name = string.IsNullOrEmpty(control.Name) ? $"[unnamed_{control.GetType().Name}]" : control.Name,
                Visible = control.Visible,
                Enabled = control.Enabled,
                Bounds = control.Bounds,
                Depth = depth,
                Children = new List<ControlNode>()
            };

            foreach (Control child in control.Controls)
            {
                node.Children.Add(BuildControlNode(child, depth + 1));
            }

            return node;
        }

        /// <summary>
        /// Gets all types from a specific assembly.
        /// </summary>
        public IEnumerable<TypeInfo> GetTypes(Assembly assembly)
        {
            try
            {
                return assembly.GetTypes()
                    .OrderBy(t => t.Namespace)
                    .ThenBy(t => t.Name)
                    .Select(t => new TypeInfo(t));
            }
            catch (ReflectionTypeLoadException ex)
            {
                // Return types that loaded successfully
                return ex.Types
                    .Where(t => t != null)
                    .OrderBy(t => t.Namespace)
                    .ThenBy(t => t.Name)
                    .Select(t => new TypeInfo(t));
            }
        }

        /// <summary>
        /// Gets all members (fields, properties, methods, events) of a type.
        /// </summary>
        public TypeMemberInfo GetTypeMembers(Type type)
        {
            return new TypeMemberInfo
            {
                Type = type,
                Fields = type.GetFields(AllMembers)
                    .Select(f => new FieldMemberInfo(f))
                    .OrderBy(f => f.Name)
                    .ToList(),
                Properties = type.GetProperties(AllMembers)
                    .Select(p => new PropertyMemberInfo(p))
                    .OrderBy(p => p.Name)
                    .ToList(),
                Methods = type.GetMethods(AllMembers)
                    .Where(m => !m.IsSpecialName) // Exclude property getters/setters
                    .Select(m => new MethodMemberInfo(m))
                    .OrderBy(m => m.Name)
                    .ToList(),
                Events = type.GetEvents(AllMembers)
                    .Select(e => new EventMemberInfo(e))
                    .OrderBy(e => e.Name)
                    .ToList()
            };
        }

        /// <summary>
        /// Gets event handlers attached to a control.
        /// </summary>
        public IEnumerable<EventHandlerInfo> GetEventHandlers(Control control)
        {
            var handlers = new List<EventHandlerInfo>();
            var controlType = control.GetType();

            // Get the EventHandlerList from the control
            var eventsField = typeof(Component).GetField("events", AllMembers);
            if (eventsField == null) yield break;

            var eventHandlerList = eventsField.GetValue(control) as EventHandlerList;
            if (eventHandlerList == null) yield break;

            // Get all event keys
            var events = controlType.GetEvents(AllMembers);

            foreach (var evt in events)
            {
                // Try to get the event key field
                var keyField = controlType.GetField($"Event{evt.Name}",
                    BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.FlattenHierarchy);

                if (keyField == null)
                {
                    // Try alternate naming convention
                    keyField = controlType.GetField($"EVENT_{evt.Name.ToUpper()}",
                        BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.FlattenHierarchy);
                }

                if (keyField != null)
                {
                    var key = keyField.GetValue(null);
                    if (key != null)
                    {
                        var handler = eventHandlerList[key];
                        if (handler != null)
                        {
                            foreach (var del in handler.GetInvocationList())
                            {
                                yield return new EventHandlerInfo
                                {
                                    EventName = evt.Name,
                                    HandlerMethod = del.Method,
                                    TargetType = del.Target?.GetType(),
                                    Target = del.Target
                                };
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Reads a private field value from an object.
        /// </summary>
        public object ReadPrivateField(object obj, string fieldName)
        {
            if (obj == null) return null;

            var field = obj.GetType().GetField(fieldName, AllMembers);
            return field?.GetValue(obj);
        }

        /// <summary>
        /// Gets all private fields and their values from an object.
        /// </summary>
        public IEnumerable<FieldValueInfo> GetPrivateFields(object obj)
        {
            if (obj == null) yield break;

            var fields = obj.GetType().GetFields(AllMembers);
            foreach (var field in fields.OrderBy(f => f.Name))
            {
                object value = null;
                string valueStr = null;
                bool hasError = false;

                try
                {
                    value = field.GetValue(obj);
                    valueStr = FormatValue(value);
                }
                catch (Exception ex)
                {
                    valueStr = $"<error: {ex.Message}>";
                    hasError = true;
                }

                yield return new FieldValueInfo
                {
                    Name = field.Name,
                    FieldType = field.FieldType,
                    Value = value,
                    ValueString = valueStr,
                    IsPrivate = field.IsPrivate,
                    IsStatic = field.IsStatic,
                    HasError = hasError
                };
            }
        }

        /// <summary>
        /// Invokes a method on an object.
        /// </summary>
        public object InvokeMethod(object obj, string methodName, params object[] args)
        {
            if (obj == null) throw new ArgumentNullException(nameof(obj));

            var method = obj.GetType().GetMethod(methodName, AllMembers);
            if (method == null)
                throw new MissingMethodException(obj.GetType().Name, methodName);

            return method.Invoke(obj, args);
        }

        /// <summary>
        /// Finds methods matching a pattern in the main MusicBee assembly.
        /// </summary>
        public IEnumerable<MethodSearchResult> FindMethods(string pattern)
        {
            if (_musicBeeAssembly == null)
                FindMainForm();

            if (_musicBeeAssembly == null)
                yield break;

            var searchPattern = pattern.ToLower();

            foreach (var type in GetTypes(_musicBeeAssembly))
            {
                foreach (var method in type.Type.GetMethods(AllMembers))
                {
                    if (method.Name.ToLower().Contains(searchPattern))
                    {
                        yield return new MethodSearchResult
                        {
                            Type = type.Type,
                            Method = method,
                            FullName = $"{type.Type.FullName}.{method.Name}"
                        };
                    }
                }
            }
        }

        /// <summary>
        /// Searches for types/methods/fields related to panels and layout.
        /// </summary>
        public IEnumerable<MethodSearchResult> FindPanelRelatedMethods()
        {
            var keywords = new[] { "panel", "arrange", "layout", "dock", "dialog" };

            foreach (var keyword in keywords)
            {
                foreach (var result in FindMethods(keyword))
                {
                    yield return result;
                }
            }
        }

        /// <summary>
        /// Gets menu items from a MenuStrip and their event handlers.
        /// </summary>
        public IEnumerable<MenuItemInfo> GetMenuItems(MenuStrip menuStrip)
        {
            foreach (ToolStripItem item in menuStrip.Items)
            {
                foreach (var menuItem in GetMenuItemsRecursive(item, ""))
                {
                    yield return menuItem;
                }
            }
        }

        private IEnumerable<MenuItemInfo> GetMenuItemsRecursive(ToolStripItem item, string path)
        {
            var currentPath = string.IsNullOrEmpty(path) ? item.Text : $"{path} > {item.Text}";

            var info = new MenuItemInfo
            {
                Path = currentPath,
                Text = item.Text,
                Name = item.Name,
                Type = item.GetType().Name,
                Enabled = item.Enabled,
                Visible = item.Visible,
                ShortcutKeys = (item as ToolStripMenuItem)?.ShortcutKeys.ToString()
            };

            // Try to get click handler
            var clickField = typeof(ToolStripItem).GetField("EventClick",
                BindingFlags.NonPublic | BindingFlags.Static);
            if (clickField != null)
            {
                var eventsField = typeof(Component).GetField("events", AllMembers);
                if (eventsField != null)
                {
                    var eventList = eventsField.GetValue(item) as EventHandlerList;
                    var key = clickField.GetValue(null);
                    if (key != null && eventList != null)
                    {
                        var handler = eventList[key];
                        if (handler != null)
                        {
                            var del = handler.GetInvocationList().FirstOrDefault();
                            if (del != null)
                            {
                                info.ClickHandler = del.Method;
                                info.HandlerTarget = del.Target?.GetType();
                            }
                        }
                    }
                }
            }

            yield return info;

            // Recurse into dropdown items
            if (item is ToolStripDropDownItem dropDown)
            {
                foreach (ToolStripItem child in dropDown.DropDownItems)
                {
                    foreach (var childInfo in GetMenuItemsRecursive(child, currentPath))
                    {
                        yield return childInfo;
                    }
                }
            }
        }

        private int CountControls(Control control)
        {
            int count = 1;
            foreach (Control child in control.Controls)
            {
                count += CountControls(child);
            }
            return count;
        }

        private string FormatValue(object value)
        {
            if (value == null) return "null";
            if (value is string s) return $"\"{s}\"";
            if (value is bool b) return b.ToString().ToLower();
            if (value is IEnumerable<object> list) return $"[{list.Count()} items]";
            if (value is System.Collections.ICollection col) return $"[{col.Count} items]";
            if (value is Control ctrl) return $"<{ctrl.GetType().Name}: {ctrl.Name}>";
            return value.ToString();
        }
    }

    #region Data Models

    public class LoadedAssemblyInfo
    {
        public Assembly Assembly { get; }
        public string Name => Assembly.GetName().Name;
        public string FullName => Assembly.FullName;
        public string Version => Assembly.GetName().Version?.ToString();
        public string Location { get; }
        public bool IsDynamic => Assembly.IsDynamic;
        public int TypeCount { get; }

        public LoadedAssemblyInfo(Assembly assembly)
        {
            Assembly = assembly;
            try { Location = assembly.Location; } catch { Location = "<dynamic>"; }
            try { TypeCount = assembly.GetTypes().Length; } catch { TypeCount = -1; }
        }

        public override string ToString() => $"{Name} v{Version} ({TypeCount} types)";
    }

    public class FormInfo
    {
        public Form Form { get; }
        public string Name => Form.Name;
        public string Text => Form.Text;
        public string Type => Form.GetType().FullName;
        public bool Visible => Form.Visible;
        public int ControlCount { get; }

        public FormInfo(Form form)
        {
            Form = form;
            ControlCount = CountControls(form);
        }

        private int CountControls(Control c)
        {
            int count = 1;
            foreach (Control child in c.Controls) count += CountControls(child);
            return count;
        }

        public override string ToString() => $"{Text} ({Type}) - {ControlCount} controls";
    }

    public class ControlNode
    {
        public Control Control { get; set; }
        public string Type { get; set; }
        public string Name { get; set; }
        public bool Visible { get; set; }
        public bool Enabled { get; set; }
        public System.Drawing.Rectangle Bounds { get; set; }
        public int Depth { get; set; }
        public List<ControlNode> Children { get; set; }

        public override string ToString()
        {
            var indent = new string(' ', Depth * 2);
            var visibility = Visible ? "" : " [HIDDEN]";
            return $"{indent}{Name} ({Type.Split('.').Last()}){visibility}";
        }
    }

    public class TypeInfo
    {
        public Type Type { get; }
        public string Name => Type.Name;
        public string FullName => Type.FullName;
        public string Namespace => Type.Namespace;
        public bool IsPublic => Type.IsPublic;
        public bool IsClass => Type.IsClass;
        public bool IsInterface => Type.IsInterface;
        public bool IsEnum => Type.IsEnum;

        public TypeInfo(Type type) => Type = type;
        public override string ToString() => FullName;
    }

    public class TypeMemberInfo
    {
        public Type Type { get; set; }
        public List<FieldMemberInfo> Fields { get; set; }
        public List<PropertyMemberInfo> Properties { get; set; }
        public List<MethodMemberInfo> Methods { get; set; }
        public List<EventMemberInfo> Events { get; set; }
    }

    public class FieldMemberInfo
    {
        public FieldInfo Field { get; }
        public string Name => Field.Name;
        public string TypeName => Field.FieldType.Name;
        public bool IsPrivate => Field.IsPrivate;
        public bool IsStatic => Field.IsStatic;

        public FieldMemberInfo(FieldInfo field) => Field = field;
        public override string ToString() => $"{(IsPrivate ? "private " : "")}{(IsStatic ? "static " : "")}{TypeName} {Name}";
    }

    public class PropertyMemberInfo
    {
        public PropertyInfo Property { get; }
        public string Name => Property.Name;
        public string TypeName => Property.PropertyType.Name;
        public bool CanRead => Property.CanRead;
        public bool CanWrite => Property.CanWrite;

        public PropertyMemberInfo(PropertyInfo prop) => Property = prop;
        public override string ToString() => $"{TypeName} {Name} {{ {(CanRead ? "get; " : "")}{(CanWrite ? "set; " : "")}}}";
    }

    public class MethodMemberInfo
    {
        public MethodInfo Method { get; }
        public string Name => Method.Name;
        public string ReturnType => Method.ReturnType.Name;
        public bool IsPrivate => Method.IsPrivate;
        public bool IsStatic => Method.IsStatic;
        public string Parameters { get; }

        public MethodMemberInfo(MethodInfo method)
        {
            Method = method;
            Parameters = string.Join(", ", method.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}"));
        }

        public override string ToString() => $"{(IsPrivate ? "private " : "")}{(IsStatic ? "static " : "")}{ReturnType} {Name}({Parameters})";
    }

    public class EventMemberInfo
    {
        public EventInfo Event { get; }
        public string Name => Event.Name;
        public string HandlerType => Event.EventHandlerType?.Name;

        public EventMemberInfo(EventInfo evt) => Event = evt;
        public override string ToString() => $"event {HandlerType} {Name}";
    }

    public class EventHandlerInfo
    {
        public string EventName { get; set; }
        public MethodInfo HandlerMethod { get; set; }
        public Type TargetType { get; set; }
        public object Target { get; set; }

        public override string ToString() =>
            $"{EventName} -> {TargetType?.Name ?? "static"}.{HandlerMethod?.Name}()";
    }

    public class FieldValueInfo
    {
        public string Name { get; set; }
        public Type FieldType { get; set; }
        public object Value { get; set; }
        public string ValueString { get; set; }
        public bool IsPrivate { get; set; }
        public bool IsStatic { get; set; }
        public bool HasError { get; set; }

        public override string ToString() =>
            $"{(IsPrivate ? "private " : "")}{FieldType?.Name} {Name} = {ValueString}";
    }

    public class MethodSearchResult
    {
        public Type Type { get; set; }
        public MethodInfo Method { get; set; }
        public string FullName { get; set; }

        public override string ToString() => FullName;
    }

    public class MenuItemInfo
    {
        public string Path { get; set; }
        public string Text { get; set; }
        public string Name { get; set; }
        public string Type { get; set; }
        public bool Enabled { get; set; }
        public bool Visible { get; set; }
        public string ShortcutKeys { get; set; }
        public MethodInfo ClickHandler { get; set; }
        public Type HandlerTarget { get; set; }

        public override string ToString()
        {
            var handler = ClickHandler != null
                ? $" -> {HandlerTarget?.Name ?? "?"}.{ClickHandler.Name}()"
                : "";
            return $"{Path}{handler}";
        }
    }

    #endregion
}
