using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Initialization;
using StellaOps.Authority.Storage.Mongo.Migrations;
using StellaOps.Authority.Storage.Mongo.Options;
using StellaOps.Authority.Storage.Mongo.Stores;

namespace StellaOps.Authority.Storage.Mongo.Extensions;

/// <summary>
/// Dependency injection helpers for wiring the Authority MongoDB storage layer.
/// </summary>
public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddAuthorityMongoStorage(
        this IServiceCollection services,
        Action<AuthorityMongoOptions> configureOptions)
    {
        ArgumentNullException.ThrowIfNull(services);
        ArgumentNullException.ThrowIfNull(configureOptions);

        services.AddOptions<AuthorityMongoOptions>()
            .Configure(configureOptions)
            .PostConfigure(static options => options.EnsureValid());

        services.TryAddSingleton(TimeProvider.System);

        services.AddSingleton<IMongoClient>(static sp =>
        {
            var options = sp.GetRequiredService<IOptions<AuthorityMongoOptions>>().Value;
            return new MongoClient(options.ConnectionString);
        });

        services.AddSingleton<IMongoDatabase>(static sp =>
        {
            var options = sp.GetRequiredService<IOptions<AuthorityMongoOptions>>().Value;
            var client = sp.GetRequiredService<IMongoClient>();

            var settings = new MongoDatabaseSettings
            {
                ReadConcern = ReadConcern.Majority,
                WriteConcern = WriteConcern.WMajority,
                ReadPreference = ReadPreference.PrimaryPreferred
            };

            var database = client.GetDatabase(options.GetDatabaseName(), settings);
            var writeConcern = database.Settings.WriteConcern.With(wTimeout: options.CommandTimeout);
            return database.WithWriteConcern(writeConcern);
        });

        services.AddSingleton<AuthorityMongoInitializer>();
        services.AddSingleton<AuthorityMongoMigrationRunner>();

        services.TryAddEnumerable(ServiceDescriptor.Singleton<IAuthorityMongoMigration, EnsureAuthorityCollectionsMigration>());

        services.AddSingleton(static sp =>
        {
            var database = sp.GetRequiredService<IMongoDatabase>();
            return database.GetCollection<AuthorityUserDocument>(AuthorityMongoDefaults.Collections.Users);
        });

        services.AddSingleton(static sp =>
        {
            var database = sp.GetRequiredService<IMongoDatabase>();
            return database.GetCollection<AuthorityClientDocument>(AuthorityMongoDefaults.Collections.Clients);
        });

        services.AddSingleton(static sp =>
        {
            var database = sp.GetRequiredService<IMongoDatabase>();
            return database.GetCollection<AuthorityScopeDocument>(AuthorityMongoDefaults.Collections.Scopes);
        });

        services.AddSingleton(static sp =>
        {
            var database = sp.GetRequiredService<IMongoDatabase>();
            return database.GetCollection<AuthorityTokenDocument>(AuthorityMongoDefaults.Collections.Tokens);
        });

        services.AddSingleton(static sp =>
        {
            var database = sp.GetRequiredService<IMongoDatabase>();
            return database.GetCollection<AuthorityLoginAttemptDocument>(AuthorityMongoDefaults.Collections.LoginAttempts);
        });

        services.AddSingleton(static sp =>
        {
            var database = sp.GetRequiredService<IMongoDatabase>();
            return database.GetCollection<AuthorityRevocationDocument>(AuthorityMongoDefaults.Collections.Revocations);
        });

        services.AddSingleton(static sp =>
        {
            var database = sp.GetRequiredService<IMongoDatabase>();
            return database.GetCollection<AuthorityRevocationExportStateDocument>(AuthorityMongoDefaults.Collections.RevocationState);
        });

        services.AddSingleton(static sp =>
        {
            var database = sp.GetRequiredService<IMongoDatabase>();
            return database.GetCollection<AuthorityBootstrapInviteDocument>(AuthorityMongoDefaults.Collections.Invites);
        });

        services.TryAddSingleton<IAuthorityCollectionInitializer, AuthorityUserCollectionInitializer>();
        services.TryAddSingleton<IAuthorityCollectionInitializer, AuthorityClientCollectionInitializer>();
        services.TryAddSingleton<IAuthorityCollectionInitializer, AuthorityScopeCollectionInitializer>();
        services.TryAddSingleton<IAuthorityCollectionInitializer, AuthorityTokenCollectionInitializer>();
        services.TryAddSingleton<IAuthorityCollectionInitializer, AuthorityLoginAttemptCollectionInitializer>();
        services.TryAddSingleton<IAuthorityCollectionInitializer, AuthorityRevocationCollectionInitializer>();
        services.TryAddSingleton<IAuthorityCollectionInitializer, AuthorityBootstrapInviteCollectionInitializer>();

        services.TryAddSingleton<IAuthorityUserStore, AuthorityUserStore>();
        services.TryAddSingleton<IAuthorityClientStore, AuthorityClientStore>();
        services.TryAddSingleton<IAuthorityScopeStore, AuthorityScopeStore>();
        services.TryAddSingleton<IAuthorityTokenStore, AuthorityTokenStore>();
        services.TryAddSingleton<IAuthorityLoginAttemptStore, AuthorityLoginAttemptStore>();
        services.TryAddSingleton<IAuthorityRevocationStore, AuthorityRevocationStore>();
        services.TryAddSingleton<IAuthorityRevocationExportStateStore, AuthorityRevocationExportStateStore>();
        services.TryAddSingleton<IAuthorityBootstrapInviteStore, AuthorityBootstrapInviteStore>();

        return services;
    }
}
