using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;

namespace Discord.Commands
{
    internal static class ReflectionUtils
    {
        private static readonly TypeInfo _objectTypeInfo = typeof(object).GetTypeInfo();

        internal static T CreateObject<T>(TypeInfo typeInfo, CommandService commands, IServiceProvider services = null)
            => CreateBuilder<T>(typeInfo, commands)(services);
        internal static Func<IServiceProvider, T> CreateBuilder<T>(TypeInfo typeInfo, CommandService commands)
        {
            var constructor = GetConstructor(typeInfo);
            var parameters = constructor.GetParameters();
            var properties = GetProperties(typeInfo);

            return (services) =>
            {
                var args = new object[parameters.Length];
                for (int i = 0; i < parameters.Length; i++)
                    args[i] = GetMember(commands, services, parameters[i].ParameterType, typeInfo);
                var obj = InvokeConstructor<T>(constructor, args, typeInfo);

                foreach(var property in properties)
                    property.SetValue(obj, GetMember(commands, services, property.PropertyType, typeInfo));
                return obj;
            };
        }
        private static T InvokeConstructor<T>(ConstructorInfo constructor, object[] args, TypeInfo ownerType)
        {
            try
            {
                return (T)constructor.Invoke(args);
            }
            catch (Exception ex)
            {
                throw new Exception($"Failed to create \"{ownerType.FullName}\"", ex);
            }
        }

        private static ConstructorInfo GetConstructor(TypeInfo ownerType)
        {
            var constructors = ownerType.DeclaredConstructors.Where(x => !x.IsStatic).ToArray();
            if (constructors.Length == 0)
                throw new InvalidOperationException($"No constructor found for \"{ownerType.FullName}\"");
            else if (constructors.Length > 1)
                throw new InvalidOperationException($"Multiple constructors found for \"{ownerType.FullName}\"");
            return constructors[0];
        }
        private static System.Reflection.PropertyInfo[] GetProperties(TypeInfo ownerType)
        {
            var result = new List<System.Reflection.PropertyInfo>();
            while (ownerType != _objectTypeInfo)
            {
                foreach (var prop in ownerType.DeclaredProperties)
                {
                    if (prop.SetMethod?.IsStatic == false && prop.SetMethod?.IsPublic == true && prop.GetCustomAttribute<DontInjectAttribute>() == null)
                        result.Add(prop);
                }
                ownerType = ownerType.BaseType.GetTypeInfo();
            }
            return result.ToArray();
        }
        private static object GetMember(CommandService commands, IServiceProvider services, Type memberType, TypeInfo ownerType)
        {
            if (memberType == typeof(CommandService))
                return commands;
            if (memberType == typeof(IServiceProvider) || memberType == services.GetType())
                return services;
            var service = services?.GetService(memberType);
            if (service != null)
                return service;
            throw new InvalidOperationException($"Failed to create \"{ownerType.FullName}\", dependency \"{memberType.Name}\" was not found.");
        }
    }
}
