using Amazon;
using Amazon.S3;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using StellaOps.Scanner.Storage.Migrations;
using StellaOps.Scanner.Storage.Mongo;
using StellaOps.Scanner.Storage.ObjectStore;
using StellaOps.Scanner.Storage.Repositories;
using StellaOps.Scanner.Storage.Services;

namespace StellaOps.Scanner.Storage.Extensions;

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddScannerStorage(this IServiceCollection services, Action<ScannerStorageOptions> configure)
    {
        ArgumentNullException.ThrowIfNull(configure);

        services.AddOptions<ScannerStorageOptions>().Configure(configure).PostConfigure(options => options.EnsureValid());
        RegisterScannerStorageServices(services);
        return services;
    }

    public static IServiceCollection AddScannerStorage(this IServiceCollection services, IConfiguration configuration)
    {
        ArgumentNullException.ThrowIfNull(configuration);

        services.AddOptions<ScannerStorageOptions>()
            .Bind(configuration)
            .PostConfigure(options => options.EnsureValid());

        RegisterScannerStorageServices(services);
        return services;
    }

    private static void RegisterScannerStorageServices(IServiceCollection services)
    {
        services.TryAddSingleton(CreateMongoClient);
        services.TryAddSingleton(CreateMongoDatabase);
        services.TryAddSingleton<MongoCollectionProvider>();
        services.TryAddEnumerable(ServiceDescriptor.Singleton<IMongoMigration, EnsureLifecycleRuleTtlMigration>());
        services.TryAddSingleton(provider =>
        {
            var migrations = provider.GetServices<IMongoMigration>();
            return new MongoMigrationRunner(
                provider.GetRequiredService<IMongoDatabase>(),
                migrations,
                provider.GetRequiredService<ILogger<MongoMigrationRunner>>(),
                TimeProvider.System);
        });

        services.TryAddSingleton<MongoBootstrapper>();
        services.TryAddSingleton<ArtifactRepository>();
        services.TryAddSingleton<ImageRepository>();
        services.TryAddSingleton<LayerRepository>();
        services.TryAddSingleton<LinkRepository>();
        services.TryAddSingleton<JobRepository>();
        services.TryAddSingleton<LifecycleRuleRepository>();

        services.TryAddSingleton(CreateAmazonS3Client);
        services.TryAddSingleton<IArtifactObjectStore, S3ArtifactObjectStore>();
        services.TryAddSingleton<ArtifactStorageService>();
    }

    private static IMongoClient CreateMongoClient(IServiceProvider provider)
    {
        var options = provider.GetRequiredService<IOptions<ScannerStorageOptions>>().Value;
        options.EnsureValid();

        var settings = MongoClientSettings.FromConnectionString(options.Mongo.ConnectionString);
        settings.RetryReads = true;
        settings.RetryWrites = true;
        settings.DirectConnection = false;
        settings.ReadPreference = ReadPreference.PrimaryPreferred;
        settings.ServerSelectionTimeout = options.Mongo.CommandTimeout;
        settings.ConnectTimeout = options.Mongo.CommandTimeout;
        settings.SocketTimeout = options.Mongo.CommandTimeout;
        settings.ReadConcern = options.Mongo.UseMajorityReadConcern ? ReadConcern.Majority : ReadConcern.Local;
        settings.WriteConcern = options.Mongo.UseMajorityWriteConcern ? WriteConcern.WMajority : WriteConcern.W1;

        return new MongoClient(settings);
    }

    private static IMongoDatabase CreateMongoDatabase(IServiceProvider provider)
    {
        var options = provider.GetRequiredService<IOptions<ScannerStorageOptions>>().Value;
        var client = provider.GetRequiredService<IMongoClient>();
        var databaseName = options.Mongo.ResolveDatabaseName();
        return client.GetDatabase(databaseName);
    }

    private static IAmazonS3 CreateAmazonS3Client(IServiceProvider provider)
    {
        var options = provider.GetRequiredService<IOptions<ScannerStorageOptions>>().Value.ObjectStore;
        var config = new AmazonS3Config
        {
            RegionEndpoint = RegionEndpoint.GetBySystemName(options.Region),
            ForcePathStyle = options.ForcePathStyle,
        };

        if (!string.IsNullOrWhiteSpace(options.ServiceUrl))
        {
            config.ServiceURL = options.ServiceUrl;
        }

        return new AmazonS3Client(config);
    }
}
