using System.Runtime.CompilerServices;
using Femto.Common;
using Femto.Common.Infrastructure;
using Femto.Common.Infrastructure.DbConnection;
using Femto.Common.Infrastructure.Outbox;
using Femto.Common.Integration;
using Femto.Modules.Auth.Contracts.Events;
using Femto.Modules.Blog.Handlers;
using Femto.Modules.Blog.Infrastructure;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Quartz;

namespace Femto.Modules.Blog.Application;

public static class BlogStartup
{
    public static void InitializeBlogModule(
        this IServiceCollection rootContainer,
        string connectionString,
        IEventBus bus,
        ILoggerFactory loggerFactory
    )
    {
        var hostBuilder = Host.CreateDefaultBuilder();

        hostBuilder.ConfigureServices(services =>
            ConfigureServices(services, connectionString, bus, loggerFactory)
        );

        var host = hostBuilder.Build();

        rootContainer.AddHostedService(_ => new BlogApplication(host));

        rootContainer.AddKeyedScoped<ScopeBinding>(
            "BlogService",
            (_, o) => new ScopeBinding(host.Services.CreateScope())
        );
        
        rootContainer.AddScoped(services =>
            services.GetRequiredKeyedService<ScopeBinding>("BlogService").GetService<IBlogModule>()
        );

        bus.Subscribe(
            (evt, cancellationToken) => EventSubscriber(evt, host.Services, cancellationToken)
        );
    }

    private static void ConfigureServices(
        this IServiceCollection services,
        string connectionString,
        IEventPublisher publisher,
        ILoggerFactory loggerFactory
    )
    {
        services.AddTransient<IDbConnectionFactory>(_ => new DbConnectionFactory(connectionString));

        services.AddDbContext<BlogContext>(builder =>
        {
            builder.UseNpgsql(
                connectionString,
                o =>
                {
                    o.MapEnum<OutboxEntryStatus>("outbox_status");
                }
            );
            builder.UseSnakeCaseNamingConvention();
            builder.UseLoggerFactory(loggerFactory);
        });

        services.AddSingleton(loggerFactory);
        services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));

        services.AddOutbox<BlogContext, OutboxMessageHandler>();

        services.AddMediatR(c =>
        {
            c.RegisterServicesFromAssembly(typeof(BlogStartup).Assembly);
        });

        services.ConfigureDomainServices<BlogContext>();

        services.AddScoped<IBlogModule, BlogModule>();

        services.AddSingleton(publisher);
    }

    private static async Task EventSubscriber(
        IEvent evt,
        IServiceProvider provider,
        CancellationToken cancellationToken
    )
    {
        using var scope = provider.CreateScope();

        var context = scope.ServiceProvider.GetRequiredService<BlogContext>();
        var loggerFactory = scope.ServiceProvider.GetRequiredService<ILoggerFactory>();
        var logger = loggerFactory.CreateLogger<BlogApplication>();
        var publisher = scope.ServiceProvider.GetRequiredService<IPublisher>();

        // todo inject these
        IEventHandler? handler = evt switch
        {
            UserWasCreatedIntegrationEvent => new UserCreatedEventHandler(
                context,
                loggerFactory.CreateLogger<UserCreatedEventHandler>()
            ),
            _ => 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);
        }
    }
}
