using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Plugin.Standard.Bootstrap;
using StellaOps.Authority.Plugin.Standard.Security;
using StellaOps.Authority.Plugin.Standard.Storage;
using StellaOps.Authority.Storage.Mongo.Stores;

namespace StellaOps.Authority.Plugin.Standard;

internal sealed class StandardPluginRegistrar : IAuthorityPluginRegistrar
{
    public string PluginType => "standard";

    public void Register(AuthorityPluginRegistrationContext context)
    {
        if (context is null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        var pluginName = context.Plugin.Manifest.Name;

        context.Services.TryAddSingleton<IPasswordHasher, Pbkdf2PasswordHasher>();
        context.Services.AddSingleton<StandardClaimsEnricher>();
        context.Services.AddSingleton<IClaimsEnricher>(sp => sp.GetRequiredService<StandardClaimsEnricher>());

        context.Services.AddOptions<StandardPluginOptions>(pluginName)
            .Bind(context.Plugin.Configuration)
            .PostConfigure(options => options.Validate(pluginName));

        context.Services.AddSingleton(sp =>
        {
            var database = sp.GetRequiredService<IMongoDatabase>();
            var optionsMonitor = sp.GetRequiredService<IOptionsMonitor<StandardPluginOptions>>();
            var pluginOptions = optionsMonitor.Get(pluginName);
            var passwordHasher = sp.GetRequiredService<IPasswordHasher>();
            var loggerFactory = sp.GetRequiredService<ILoggerFactory>();

            return new StandardUserCredentialStore(
                pluginName,
                database,
                pluginOptions,
                passwordHasher,
                loggerFactory.CreateLogger<StandardUserCredentialStore>());
        });

        context.Services.AddSingleton(sp =>
        {
            var clientStore = sp.GetRequiredService<IAuthorityClientStore>();
            return new StandardClientProvisioningStore(pluginName, clientStore);
        });

        context.Services.AddSingleton<IIdentityProviderPlugin>(sp =>
        {
            var store = sp.GetRequiredService<StandardUserCredentialStore>();
            var clientProvisioningStore = sp.GetRequiredService<StandardClientProvisioningStore>();
            var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
            return new StandardIdentityProviderPlugin(
                context.Plugin,
                store,
                clientProvisioningStore,
                sp.GetRequiredService<StandardClaimsEnricher>(),
                loggerFactory.CreateLogger<StandardIdentityProviderPlugin>());
        });

        context.Services.AddSingleton<IClientProvisioningStore>(sp =>
            sp.GetRequiredService<StandardClientProvisioningStore>());

        context.Services.AddSingleton<IHostedService>(sp =>
            new StandardPluginBootstrapper(
                pluginName,
                sp.GetRequiredService<IOptionsMonitor<StandardPluginOptions>>(),
                sp.GetRequiredService<StandardUserCredentialStore>(),
                sp.GetRequiredService<ILogger<StandardPluginBootstrapper>>()));
    }
}
