using Books.Api.EventFlow.Extensions;
using Books.Api.EventFlow.Infrastructure;
using Books.Api.GraphQL;
using Books.Api.Infrastructure;
using Books.Api.Logging;
using EventFlow;
using EventFlow.Configuration;
using EventFlow.Extensions;
using EventFlow.Hangfire.Extensions;
using EventFlow.PostgreSql.Connections;
using EventFlow.PostgreSql.Extensions;
using EventFlow.ReadStores;
using EventFlow.Subscribers;
using GraphQL;
using Hangfire;
using Hangfire.PostgreSql;
using Npgsql;

namespace Books.Api;

public static class Startup
{
    public static void ConfigureServices(IServiceCollection services, IConfiguration config, IHostEnvironment? environment = null)
    {
        var connectionString = config.GetConnectionString("Default")
            ?? throw new InvalidOperationException("Connection string 'Default' not found");

        // Run database migrations (skipped in Test environment where migrations run separately with test connection string)
        var isTestEnvironment = environment?.EnvironmentName == "Test";
        if (!isTestEnvironment)
        {
            DatabaseMigrator.Migrate(connectionString);
        }

        // PostgreSQL data source
        var dataSource = new NpgsqlDataSourceBuilder(connectionString).Build();
        services.AddSingleton(dataSource);

        // Hangfire
        services.AddHangfire(c => c
            .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
            .UseSimpleAssemblyNameTypeSerializer()
            .UseRecommendedSerializerSettings()
            .UsePostgreSqlStorage(o => o.UseNpgsqlConnection(connectionString)));
        services.AddHangfireServer();

        // Scheduler abstraction over Hangfire
        services.AddSingleton<IScheduler, HangfireScheduler>();

        // EventFlow
        services.AddEventFlow(o => o
            .UsePostgreSqlEventStore()
            .ConfigurePostgreSql(PostgreSqlConfiguration.New.SetConnectionString(connectionString))
            .AddDefaults(typeof(Startup).Assembly)
            .AddReadModels()
            .Configure(c => c.IsAsynchronousSubscribersEnabled = true)
            .UseHangfireJobScheduler());

        // Resilience strategies
        services.AddSingleton<IDispatchToReadStoresResilienceStrategy, ReadStoresResilienceStrategy>();
        services.AddSingleton<IDispatchToSubscriberResilienceStrategy, DispatchToSubscriberResilienceStrategy>();

        // Read model repositories
        services.AddRepositories();

        // Logging decorators
        services.DecorateAsyncEventHandlersWithLogging();

        // GraphQL
        services.AddGraphQL(builder => builder
            .AddSchema<BooksSchema>()
            .AddSystemTextJson()
            .AddDataLoader()
            .AddGraphTypes(typeof(BooksSchema).Assembly)
            .AddErrorInfoProvider(opt => opt.ExposeExceptionDetails = true));
    }
}
