using System.Reflection;
using FluentValidation;
using LiteCqrs.Behaviors;
using LiteCqrs.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using TeleWave.Application.Broadcast.Bumpers;
using TeleWave.Application.Common.Behaviors;
using TeleWave.Application.Library.Collections;
using TeleWave.Application.Library.Collections.Suggest;
using TeleWave.Application.Library.Genres;
using TeleWave.Application.Media;
using TeleWave.Application.Media.ManualInbox;
using TeleWave.Application.Media.MovieImport;
using TeleWave.Application.Metadata;
using TeleWave.Application.Programming.Groups;
using TeleWave.Application.Programming.Groups.Suggest;
using TeleWave.Application.Programming.Planning;
using TeleWave.Application.Programming.Planning.DebugExport;
using TeleWave.Application.Programming.Templates;
using TeleWave.Application.Programming.Templates.Generate;

namespace TeleWave.Application;

public static class DependencyInjection
{
    public static IServiceCollection AddApplication(this IServiceCollection services)
    {
        var assembly = typeof(DependencyInjection).Assembly;

        services.AddLiteCqrs(cqrs =>
        {
            cqrs.RegisterServicesFromAssembly(assembly);
            cqrs.Lifetime = ServiceLifetime.Scoped;

            // Порядок важен: Logging (внешний) -> Validation -> UnitOfWork (ближе всего к хендлеру).
            cqrs.AddOpenBehavior(typeof(LoggingBehavior<,>));
            cqrs.AddOpenBehavior(typeof(ValidationBehavior<,>));
            cqrs.AddOpenBehavior(typeof(UnitOfWorkBehavior<,>));
        });

        RegisterClosedGeneric(services, assembly, typeof(IValidator<>));

        // Сервисы самого слоя приложения — не хендлеры, а общие для них помощники. Регистрируются
        // здесь, а не в Infrastructure: тот слой не должен знать внутреннего устройства Application.
        services.AddScoped<BumperSpecLoader>();
        services.AddScoped<GenreMatcher>();
        services.AddScoped<ShowMetadataApplier>();
        services.AddScoped<MovieMatcher>();
        services.AddScoped<ManualInboxTaker>();
        services.AddScoped<ShowMediaEraser>();
        services.AddScoped<CollectionSuggestionBuilder>();
        services.AddScoped<CollectionPosterPicker>();
        services.AddScoped<GroupElementResolver>();
        services.AddScoped<GroupFilterMatcher>();
        services.AddScoped<DynamicGroupResolver>();
        services.AddScoped<GroupStatsService>();
        services.AddScoped<GroupSuggestionBuilder>();
        services.AddScoped<GroupMembershipCleaner>();
        services.AddScoped<InterstitialGroups>();
        services.AddScoped<SlotWriter>();
        services.AddScoped<GroupCatalog>();
        services.AddScoped<GridPlanner>();
        services.AddScoped<GroupExpander>();
        services.AddScoped<BumperResolver>();
        services.AddScoped<PostCheckRunner>();
        services.AddScoped<GridScheduleGenerator>();
        services.AddScoped<ChannelDebugCollector>();

        return services;
    }

    private static void RegisterClosedGeneric(
        IServiceCollection services,
        Assembly assembly,
        Type openInterface
    )
    {
        var implementations = assembly
            .GetTypes()
            .Where(t => t is { IsClass: true, IsAbstract: false })
            .SelectMany(t =>
                t.GetInterfaces()
                    .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == openInterface)
                    .Select(i => (Service: i, Implementation: t))
            );

        foreach (var (service, implementation) in implementations)
            services.AddScoped(service, implementation);
    }
}
