using Femto.Common.Infrastructure;
using Femto.Common.Infrastructure.DbConnection;
using Femto.Common.Infrastructure.Outbox;
using Femto.Common.Integration;
using Femto.Modules.Auth.Data;
using Femto.Modules.Auth.Infrastructure;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Quartz;

namespace Femto.Modules.Auth.Application;

public static class AuthStartup
{
    public static void InitializeAuthenticationModule(this IServiceCollection rootContainer,
        string connectionString, IEventBus eventBus)
    {
        var hostBuilder = Host.CreateDefaultBuilder();
        hostBuilder.ConfigureServices(services => ConfigureServices(services, connectionString, eventBus));
        var host = hostBuilder.Build();
        
        rootContainer.AddScoped(_ => new ScopeBinding(host.Services.CreateScope()));
        
        rootContainer.AddScoped<IAuthModule>(services =>
            services.GetRequiredService<ScopeBinding>().GetService<IAuthModule>());
        
        rootContainer.AddHostedService(services => new AuthApplication(host));
        eventBus.Subscribe((evt, cancellationToken) => EventSubscriber(evt, host.Services, cancellationToken));
    }

    private static void ConfigureServices(IServiceCollection services, string connectionString, IEventPublisher publisher)
    {
        services.AddTransient<IDbConnectionFactory>(_ => new DbConnectionFactory(connectionString));
        
        services.AddDbContext<AuthContext>(builder =>
        {
            builder.UseNpgsql(connectionString);
            builder.UseSnakeCaseNamingConvention();
            var loggerFactory = LoggerFactory.Create(b => { });
            builder.UseLoggerFactory(loggerFactory);
            // #if DEBUG
            // builder.EnableSensitiveDataLogging();
            // #endif
        });

        services.AddQuartzHostedService(options =>
        {
            options.WaitForJobsToComplete = true;
        });

        services.AddOutbox<AuthContext, OutboxMessageHandler>();

        services.AddMediatR(c => c.RegisterServicesFromAssembly(typeof(AuthStartup).Assembly));

        services.ConfigureDomainServices<AuthContext>();

        services.AddSingleton(publisher);
        
        services.AddScoped<IAuthModule, AuthModule>();
    }
    
    private static async Task EventSubscriber(
        IEvent evt,
        IServiceProvider provider,
        CancellationToken cancellationToken
    )
    {
        using var scope = provider.CreateScope();

        var context = scope.ServiceProvider.GetRequiredService<AuthContext>();
        var logger = scope.ServiceProvider.GetRequiredService<ILogger<AuthApplication>>();
        var publisher = scope.ServiceProvider.GetRequiredService<IPublisher>();

        IEventHandler? handler = evt switch
        {
            _ => null,
        };

        if (handler is null)
            return;

        await handler.Handle(evt, cancellationToken);

        if (context.ChangeTracker.HasChanges())
        {
            await context.EmitDomainEvents(logger, publisher, cancellationToken);
            await context.SaveChangesAsync(cancellationToken);
        }
    }
}
