using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using NLog;

namespace MusicBeePlugin.Clouseau.Introspection
{
    /// <summary>
    /// Provides method invocation capabilities via reflection.
    /// Supports invoking static and instance methods with automatic parameter parsing.
    /// </summary>
    public class MethodInvoker
    {
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

        private static readonly BindingFlags AllMembers =
            BindingFlags.Public | BindingFlags.NonPublic |
            BindingFlags.Instance | BindingFlags.Static;

        private static readonly BindingFlags PublicMembers =
            BindingFlags.Public |
            BindingFlags.Instance | BindingFlags.Static;

        /// <summary>
        /// Result of a method invocation attempt.
        /// </summary>
        public class InvocationResult
        {
            /// <summary>
            /// Whether the invocation succeeded.
            /// </summary>
            public bool Success { get; set; }

            /// <summary>
            /// The return value from the method, if any.
            /// </summary>
            public object ReturnValue { get; set; }

            /// <summary>
            /// The return type of the method.
            /// </summary>
            public Type ReturnType { get; set; }

            /// <summary>
            /// Error message if the invocation failed.
            /// </summary>
            public string ErrorMessage { get; set; }

            /// <summary>
            /// The exception that occurred, if any.
            /// </summary>
            public Exception Exception { get; set; }

            /// <summary>
            /// Time taken to execute the method in milliseconds.
            /// </summary>
            public double ExecutionTimeMs { get; set; }

            /// <summary>
            /// Formatted string representation of the return value.
            /// </summary>
            public string FormattedReturnValue { get; set; }
        }

        /// <summary>
        /// Information about a method for display in the UI.
        /// </summary>
        public class MethodDisplayInfo
        {
            public MethodInfo Method { get; set; }
            public string Name => Method?.Name;
            public string Signature { get; set; }
            public string ReturnType { get; set; }
            public bool IsStatic { get; set; }
            public bool IsPublic { get; set; }
            public ParameterDisplayInfo[] Parameters { get; set; }
            public Type DeclaringType { get; set; }

            public override string ToString() => Signature ?? Name ?? "(unknown)";
        }

        /// <summary>
        /// Information about a method parameter for display in the UI.
        /// </summary>
        public class ParameterDisplayInfo
        {
            public ParameterInfo Parameter { get; set; }
            public string Name => Parameter?.Name;
            public Type ParameterType => Parameter?.ParameterType;
            public string TypeName { get; set; }
            public bool HasDefaultValue => Parameter?.HasDefaultValue ?? false;
            public object DefaultValue => Parameter?.DefaultValue;
            public bool IsOptional => Parameter?.IsOptional ?? false;
            public bool IsOut => Parameter?.IsOut ?? false;
            public bool IsRef => Parameter?.ParameterType?.IsByRef ?? false;
            public string[] EnumValues { get; set; }

            public override string ToString() => $"{TypeName} {Name}";
        }

        /// <summary>
        /// Invokes a static method on a type.
        /// </summary>
        /// <param name="type">The type containing the method.</param>
        /// <param name="methodName">The name of the method to invoke.</param>
        /// <param name="args">Arguments to pass to the method.</param>
        /// <returns>Result of the invocation.</returns>
        public InvocationResult InvokeStaticMethod(Type type, string methodName, object[] args)
        {
            if (type == null)
                return new InvocationResult { Success = false, ErrorMessage = "Type cannot be null" };

            if (string.IsNullOrEmpty(methodName))
                return new InvocationResult { Success = false, ErrorMessage = "Method name cannot be null or empty" };

            try
            {
                var methods = type.GetMethods(AllMembers)
                    .Where(m => m.Name == methodName && m.IsStatic)
                    .ToArray();

                if (methods.Length == 0)
                    return new InvocationResult { Success = false, ErrorMessage = $"Static method '{methodName}' not found on type '{type.FullName}'" };

                // Find the best matching method based on argument count
                var method = FindBestMethod(methods, args?.Length ?? 0);
                if (method == null)
                    return new InvocationResult { Success = false, ErrorMessage = $"No overload of '{methodName}' matches the provided arguments" };

                return InvokeMethod(null, method, args);
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Error invoking static method {0} on {1}", methodName, type.FullName);
                return new InvocationResult
                {
                    Success = false,
                    ErrorMessage = ex.Message,
                    Exception = ex
                };
            }
        }

        /// <summary>
        /// Invokes an instance method on an object.
        /// </summary>
        /// <param name="instance">The object instance to invoke the method on.</param>
        /// <param name="methodName">The name of the method to invoke.</param>
        /// <param name="args">Arguments to pass to the method.</param>
        /// <returns>Result of the invocation.</returns>
        public InvocationResult InvokeInstanceMethod(object instance, string methodName, object[] args)
        {
            if (instance == null)
                return new InvocationResult { Success = false, ErrorMessage = "Instance cannot be null" };

            if (string.IsNullOrEmpty(methodName))
                return new InvocationResult { Success = false, ErrorMessage = "Method name cannot be null or empty" };

            try
            {
                var type = instance.GetType();
                var methods = type.GetMethods(AllMembers)
                    .Where(m => m.Name == methodName && !m.IsStatic)
                    .ToArray();

                if (methods.Length == 0)
                    return new InvocationResult { Success = false, ErrorMessage = $"Instance method '{methodName}' not found on type '{type.FullName}'" };

                // Find the best matching method based on argument count
                var method = FindBestMethod(methods, args?.Length ?? 0);
                if (method == null)
                    return new InvocationResult { Success = false, ErrorMessage = $"No overload of '{methodName}' matches the provided arguments" };

                return InvokeMethod(instance, method, args);
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Error invoking instance method {0}", methodName);
                return new InvocationResult
                {
                    Success = false,
                    ErrorMessage = ex.Message,
                    Exception = ex
                };
            }
        }

        /// <summary>
        /// Invokes a specific method with the given arguments.
        /// </summary>
        /// <param name="instance">The object instance (null for static methods).</param>
        /// <param name="method">The method to invoke.</param>
        /// <param name="args">Arguments to pass to the method.</param>
        /// <returns>Result of the invocation.</returns>
        public InvocationResult InvokeMethod(object instance, MethodInfo method, object[] args)
        {
            if (method == null)
                return new InvocationResult { Success = false, ErrorMessage = "Method cannot be null" };

            var result = new InvocationResult
            {
                ReturnType = method.ReturnType
            };

            var stopwatch = System.Diagnostics.Stopwatch.StartNew();

            try
            {
                // Prepare arguments with default values if needed
                var parameters = method.GetParameters();
                var preparedArgs = PrepareArguments(parameters, args);

                result.ReturnValue = method.Invoke(instance, preparedArgs);
                result.Success = true;
                result.FormattedReturnValue = FormatValue(result.ReturnValue, method.ReturnType);

                Logger.Info("Successfully invoked {0}.{1}", method.DeclaringType?.Name, method.Name);
            }
            catch (TargetInvocationException ex)
            {
                // Unwrap the inner exception for better error messages
                var innerEx = ex.InnerException ?? ex;
                result.Success = false;
                result.ErrorMessage = innerEx.Message;
                result.Exception = innerEx;

                Logger.Error(innerEx, "Error during method invocation {0}.{1}", method.DeclaringType?.Name, method.Name);
            }
            catch (Exception ex)
            {
                result.Success = false;
                result.ErrorMessage = ex.Message;
                result.Exception = ex;

                Logger.Error(ex, "Error invoking method {0}.{1}", method.DeclaringType?.Name, method.Name);
            }
            finally
            {
                stopwatch.Stop();
                result.ExecutionTimeMs = stopwatch.Elapsed.TotalMilliseconds;
            }

            return result;
        }

        /// <summary>
        /// Gets a human-readable method signature.
        /// </summary>
        /// <param name="method">The method to describe.</param>
        /// <returns>A formatted signature string.</returns>
        public string GetMethodSignature(MethodInfo method)
        {
            if (method == null) return "(null)";

            var sb = new StringBuilder();

            // Access modifier
            if (method.IsPublic) sb.Append("public ");
            else if (method.IsPrivate) sb.Append("private ");
            else if (method.IsFamily) sb.Append("protected ");
            else if (method.IsAssembly) sb.Append("internal ");

            // Static modifier
            if (method.IsStatic) sb.Append("static ");

            // Virtual/Override
            if (method.IsVirtual && !method.IsFinal) sb.Append("virtual ");
            if (method.IsAbstract) sb.Append("abstract ");

            // Return type
            sb.Append(GetTypeName(method.ReturnType));
            sb.Append(' ');

            // Method name
            sb.Append(method.Name);

            // Generic type parameters
            if (method.IsGenericMethod)
            {
                sb.Append('<');
                sb.Append(string.Join(", ", method.GetGenericArguments().Select(GetTypeName)));
                sb.Append('>');
            }

            // Parameters
            sb.Append('(');
            var parameters = method.GetParameters();
            for (int i = 0; i < parameters.Length; i++)
            {
                if (i > 0) sb.Append(", ");

                var param = parameters[i];

                if (param.IsOut)
                    sb.Append("out ");
                else if (param.ParameterType.IsByRef)
                    sb.Append("ref ");
                else if (param.IsIn)
                    sb.Append("in ");

                sb.Append(GetTypeName(param.ParameterType));
                sb.Append(' ');
                sb.Append(param.Name);

                if (param.HasDefaultValue)
                {
                    sb.Append(" = ");
                    sb.Append(FormatDefaultValue(param.DefaultValue, param.ParameterType));
                }
            }
            sb.Append(')');

            return sb.ToString();
        }

        /// <summary>
        /// Gets detailed information about a method for UI display.
        /// </summary>
        /// <param name="method">The method to describe.</param>
        /// <returns>Display information for the method.</returns>
        public MethodDisplayInfo GetMethodDisplayInfo(MethodInfo method)
        {
            if (method == null) return null;

            var parameters = method.GetParameters();
            return new MethodDisplayInfo
            {
                Method = method,
                Signature = GetMethodSignature(method),
                ReturnType = GetTypeName(method.ReturnType),
                IsStatic = method.IsStatic,
                IsPublic = method.IsPublic,
                DeclaringType = method.DeclaringType,
                Parameters = parameters.Select(p => new ParameterDisplayInfo
                {
                    Parameter = p,
                    TypeName = GetTypeName(p.ParameterType),
                    EnumValues = p.ParameterType.IsEnum
                        ? Enum.GetNames(p.ParameterType)
                        : null
                }).ToArray()
            };
        }

        /// <summary>
        /// Gets all methods on a type, optionally filtered.
        /// </summary>
        /// <param name="type">The type to inspect.</param>
        /// <param name="includePrivate">Whether to include non-public methods.</param>
        /// <param name="includeInherited">Whether to include inherited methods.</param>
        /// <returns>Collection of method display info.</returns>
        public IEnumerable<MethodDisplayInfo> GetMethods(Type type, bool includePrivate = false, bool includeInherited = false)
        {
            if (type == null) yield break;

            var flags = includePrivate ? AllMembers : PublicMembers;
            if (!includeInherited)
                flags |= BindingFlags.DeclaredOnly;

            var methods = type.GetMethods(flags)
                .Where(m => !m.IsSpecialName) // Exclude property getters/setters
                .OrderBy(m => m.IsStatic ? 0 : 1)
                .ThenBy(m => m.Name);

            foreach (var method in methods)
            {
                yield return GetMethodDisplayInfo(method);
            }
        }

        /// <summary>
        /// Parses a string input to the target type.
        /// Supports common types: string, int, long, float, double, bool, enum, DateTime, Guid.
        /// </summary>
        /// <param name="input">The string input to parse.</param>
        /// <param name="targetType">The type to convert to.</param>
        /// <returns>The parsed value.</returns>
        public object ParseParameter(string input, Type targetType)
        {
            if (targetType == null)
                throw new ArgumentNullException(nameof(targetType));

            // Handle null or empty input
            if (string.IsNullOrEmpty(input))
            {
                if (targetType.IsValueType && Nullable.GetUnderlyingType(targetType) == null)
                    return Activator.CreateInstance(targetType); // Default value for value types
                return null;
            }

            // Handle "null" string
            if (input.Equals("null", StringComparison.OrdinalIgnoreCase))
            {
                if (targetType.IsValueType && Nullable.GetUnderlyingType(targetType) == null)
                    throw new ArgumentException($"Cannot convert 'null' to value type {targetType.Name}");
                return null;
            }

            // Handle nullable types
            var underlyingType = Nullable.GetUnderlyingType(targetType);
            if (underlyingType != null)
                targetType = underlyingType;

            // Handle by-ref types
            if (targetType.IsByRef)
                targetType = targetType.GetElementType();

            // String - no conversion needed
            if (targetType == typeof(string))
                return input;

            // Boolean
            if (targetType == typeof(bool))
            {
                if (bool.TryParse(input, out bool boolResult))
                    return boolResult;
                if (input == "1" || input.Equals("yes", StringComparison.OrdinalIgnoreCase))
                    return true;
                if (input == "0" || input.Equals("no", StringComparison.OrdinalIgnoreCase))
                    return false;
                throw new ArgumentException($"Cannot convert '{input}' to boolean");
            }

            // Integer types
            if (targetType == typeof(int))
            {
                if (int.TryParse(input, NumberStyles.Integer, CultureInfo.InvariantCulture, out int intResult))
                    return intResult;
                throw new ArgumentException($"Cannot convert '{input}' to int");
            }

            if (targetType == typeof(long))
            {
                if (long.TryParse(input, NumberStyles.Integer, CultureInfo.InvariantCulture, out long longResult))
                    return longResult;
                throw new ArgumentException($"Cannot convert '{input}' to long");
            }

            if (targetType == typeof(short))
            {
                if (short.TryParse(input, NumberStyles.Integer, CultureInfo.InvariantCulture, out short shortResult))
                    return shortResult;
                throw new ArgumentException($"Cannot convert '{input}' to short");
            }

            if (targetType == typeof(byte))
            {
                if (byte.TryParse(input, NumberStyles.Integer, CultureInfo.InvariantCulture, out byte byteResult))
                    return byteResult;
                throw new ArgumentException($"Cannot convert '{input}' to byte");
            }

            // Floating point types
            if (targetType == typeof(float))
            {
                if (float.TryParse(input, NumberStyles.Float, CultureInfo.InvariantCulture, out float floatResult))
                    return floatResult;
                throw new ArgumentException($"Cannot convert '{input}' to float");
            }

            if (targetType == typeof(double))
            {
                if (double.TryParse(input, NumberStyles.Float, CultureInfo.InvariantCulture, out double doubleResult))
                    return doubleResult;
                throw new ArgumentException($"Cannot convert '{input}' to double");
            }

            if (targetType == typeof(decimal))
            {
                if (decimal.TryParse(input, NumberStyles.Number, CultureInfo.InvariantCulture, out decimal decimalResult))
                    return decimalResult;
                throw new ArgumentException($"Cannot convert '{input}' to decimal");
            }

            // Enum
            if (targetType.IsEnum)
            {
                // Try parsing as name first (case insensitive)
                try
                {
                    var enumResult = Enum.Parse(targetType, input, ignoreCase: true);
                    return enumResult;
                }
                catch (ArgumentException)
                {
                    // Not a valid enum name, continue to try as integer
                }

                // Try parsing as integer value
                if (int.TryParse(input, out int enumIntValue))
                {
                    if (Enum.IsDefined(targetType, enumIntValue))
                        return Enum.ToObject(targetType, enumIntValue);
                }

                throw new ArgumentException($"Cannot convert '{input}' to enum {targetType.Name}. Valid values: {string.Join(", ", Enum.GetNames(targetType))}");
            }

            // DateTime
            if (targetType == typeof(DateTime))
            {
                if (DateTime.TryParse(input, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateResult))
                    return dateResult;
                throw new ArgumentException($"Cannot convert '{input}' to DateTime");
            }

            // TimeSpan
            if (targetType == typeof(TimeSpan))
            {
                if (TimeSpan.TryParse(input, CultureInfo.InvariantCulture, out TimeSpan timeResult))
                    return timeResult;
                throw new ArgumentException($"Cannot convert '{input}' to TimeSpan");
            }

            // Guid
            if (targetType == typeof(Guid))
            {
                if (Guid.TryParse(input, out Guid guidResult))
                    return guidResult;
                throw new ArgumentException($"Cannot convert '{input}' to Guid");
            }

            // Char
            if (targetType == typeof(char))
            {
                if (input.Length == 1)
                    return input[0];
                throw new ArgumentException($"Cannot convert '{input}' to char - expected single character");
            }

            // Try using TypeConverter as fallback
            try
            {
                var converter = TypeDescriptor.GetConverter(targetType);
                if (converter != null && converter.CanConvertFrom(typeof(string)))
                {
                    return converter.ConvertFromString(input);
                }
            }
            catch
            {
                // Fall through to throw
            }

            throw new ArgumentException($"Cannot convert '{input}' to type {targetType.Name}");
        }

        /// <summary>
        /// Attempts to parse a parameter, returning success status.
        /// </summary>
        /// <param name="input">The string input to parse.</param>
        /// <param name="targetType">The type to convert to.</param>
        /// <param name="result">The parsed result, or null on failure.</param>
        /// <param name="errorMessage">Error message if parsing failed.</param>
        /// <returns>True if parsing succeeded.</returns>
        public bool TryParseParameter(string input, Type targetType, out object result, out string errorMessage)
        {
            result = null;
            errorMessage = null;

            try
            {
                result = ParseParameter(input, targetType);
                return true;
            }
            catch (Exception ex)
            {
                errorMessage = ex.Message;
                return false;
            }
        }

        /// <summary>
        /// Gets a friendly type name, handling generics and common types.
        /// </summary>
        /// <param name="type">The type to describe.</param>
        /// <returns>A human-readable type name.</returns>
        public string GetTypeName(Type type)
        {
            if (type == null) return "void";

            // Handle common aliases
            if (type == typeof(void)) return "void";
            if (type == typeof(int)) return "int";
            if (type == typeof(long)) return "long";
            if (type == typeof(short)) return "short";
            if (type == typeof(byte)) return "byte";
            if (type == typeof(bool)) return "bool";
            if (type == typeof(float)) return "float";
            if (type == typeof(double)) return "double";
            if (type == typeof(decimal)) return "decimal";
            if (type == typeof(string)) return "string";
            if (type == typeof(object)) return "object";
            if (type == typeof(char)) return "char";

            // Handle by-ref types
            if (type.IsByRef)
                return GetTypeName(type.GetElementType());

            // Handle nullable types
            var underlying = Nullable.GetUnderlyingType(type);
            if (underlying != null)
                return GetTypeName(underlying) + "?";

            // Handle arrays
            if (type.IsArray)
                return GetTypeName(type.GetElementType()) + "[]";

            // Handle generic types
            if (type.IsGenericType)
            {
                var genericDef = type.GetGenericTypeDefinition();
                var baseName = genericDef.Name;
                var backtickIndex = baseName.IndexOf('`');
                if (backtickIndex > 0)
                    baseName = baseName.Substring(0, backtickIndex);

                var typeArgs = string.Join(", ", type.GetGenericArguments().Select(GetTypeName));
                return $"{baseName}<{typeArgs}>";
            }

            return type.Name;
        }

        #region Private Helper Methods

        private MethodInfo FindBestMethod(MethodInfo[] methods, int argCount)
        {
            // First, try to find an exact match
            var exactMatch = methods.FirstOrDefault(m =>
            {
                var parameters = m.GetParameters();
                var requiredCount = parameters.Count(p => !p.IsOptional && !p.HasDefaultValue);
                var totalCount = parameters.Length;
                return argCount >= requiredCount && argCount <= totalCount;
            });

            if (exactMatch != null)
                return exactMatch;

            // Fall back to the first method with compatible parameter count
            return methods.FirstOrDefault(m =>
            {
                var parameters = m.GetParameters();
                return argCount <= parameters.Length;
            });
        }

        private object[] PrepareArguments(ParameterInfo[] parameters, object[] args)
        {
            if (parameters.Length == 0)
                return Array.Empty<object>();

            var preparedArgs = new object[parameters.Length];
            var argsLength = args?.Length ?? 0;

            for (int i = 0; i < parameters.Length; i++)
            {
                if (i < argsLength && args[i] != null)
                {
                    preparedArgs[i] = args[i];
                }
                else if (parameters[i].HasDefaultValue)
                {
                    preparedArgs[i] = parameters[i].DefaultValue;
                }
                else if (parameters[i].IsOptional)
                {
                    preparedArgs[i] = Type.Missing;
                }
                else if (parameters[i].ParameterType.IsValueType)
                {
                    preparedArgs[i] = Activator.CreateInstance(parameters[i].ParameterType);
                }
                else
                {
                    preparedArgs[i] = null;
                }
            }

            return preparedArgs;
        }

        private string FormatValue(object value, Type type)
        {
            if (value == null)
                return "null";

            if (type == typeof(void))
                return "(void)";

            if (value is string s)
                return $"\"{s}\"";

            if (value is char c)
                return $"'{c}'";

            if (value is bool b)
                return b.ToString().ToLower();

            if (value is DateTime dt)
                return dt.ToString("yyyy-MM-dd HH:mm:ss.fff");

            if (value is TimeSpan ts)
                return ts.ToString();

            if (value is System.Collections.IEnumerable enumerable && !(value is string))
            {
                var items = enumerable.Cast<object>().Take(10).ToList();
                var preview = string.Join(", ", items.Select(i => i?.ToString() ?? "null"));
                if (items.Count >= 10)
                    preview += ", ...";
                return $"[{preview}]";
            }

            return value.ToString();
        }

        private string FormatDefaultValue(object value, Type type)
        {
            if (value == null)
                return "null";

            if (value is string s)
                return $"\"{s}\"";

            if (value is char c)
                return $"'{c}'";

            if (value is bool b)
                return b.ToString().ToLower();

            if (type.IsEnum)
                return $"{type.Name}.{value}";

            return value.ToString();
        }

        #endregion
    }
}
