using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StellaOps.DependencyInjection;
using StellaOps.Plugin.Hosting;
using StellaOps.Plugin.Internal;
using System;
using System.Collections.Generic;
using System.Linq;

namespace StellaOps.Plugin.DependencyInjection;

public static class PluginDependencyInjectionExtensions
{
    public static IServiceCollection RegisterPluginRoutines(
        this IServiceCollection services,
        IConfiguration configuration,
        PluginHostOptions options,
        ILogger? logger = null)
    {
        if (services == null)
        {
            throw new ArgumentNullException(nameof(services));
        }

        if (configuration == null)
        {
            throw new ArgumentNullException(nameof(configuration));
        }

        if (options == null)
        {
            throw new ArgumentNullException(nameof(options));
        }

        var loadResult = PluginHost.LoadPlugins(options, logger);

        foreach (var plugin in loadResult.Plugins)
        {
            foreach (var routine in CreateRoutines(plugin.Assembly))
            {
                logger?.LogDebug(
                    "Registering DI routine '{RoutineType}' from plugin '{PluginAssembly}'.",
                    routine.GetType().FullName,
                    plugin.Assembly.FullName);

                routine.Register(services, configuration);
            }
        }

        if (loadResult.MissingOrderedPlugins.Count > 0)
        {
            logger?.LogWarning(
                "Some ordered plugins were not found: {Missing}",
                string.Join(", ", loadResult.MissingOrderedPlugins));
        }

        return services;
    }

    private static IEnumerable<IDependencyInjectionRoutine> CreateRoutines(System.Reflection.Assembly assembly)
    {
        foreach (var type in assembly.GetLoadableTypes())
        {
            if (type is null || type.IsAbstract || type.IsInterface)
            {
                continue;
            }

            if (!typeof(IDependencyInjectionRoutine).IsAssignableFrom(type))
            {
                continue;
            }

            object? instance;
            try
            {
                instance = Activator.CreateInstance(type);
            }
            catch
            {
                continue;
            }

            if (instance is IDependencyInjectionRoutine routine)
            {
                yield return routine;
            }
        }
    }
}