using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using NSubstitute;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
using ZB.MOM.WW.ScadaBridge.NotificationOutbox.Delivery;
using ZB.MOM.WW.ScadaBridge.NotificationService;

namespace ZB.MOM.WW.ScadaBridge.NotificationOutbox.Tests;

/// <summary>
/// Task 17: Tests for <see cref="ServiceCollectionExtensions.AddNotificationOutbox"/> — the
/// DI registration extension for the Notification Outbox component. The extension binds
/// <see cref="NotificationOutboxOptions"/> from the <c>ScadaBridge:NotificationOutbox</c>
/// configuration section and registers the channel delivery adapter(s).
///
/// The Host wires both <c>AddNotificationService</c> and <c>AddNotificationOutbox</c> on the
/// central node; these tests do the same so the
/// adapter's SMTP dependencies (<c>Func&lt;ISmtpClientWrapper&gt;</c>, <c>OAuth2TokenService</c>,
/// <c>NotificationOptions</c>) are satisfied. <see cref="INotificationRepository"/> — which the
/// <see cref="EmailNotificationDeliveryAdapter"/> takes directly and is registered scoped by the
/// Configuration Database component — is supplied here by a lightweight stub.
/// </summary>
public class ServiceRegistrationTests
{
    private static ServiceProvider BuildProvider()
    {
        var services = new ServiceCollection();

        // Empty configuration: the options binding must still succeed and yield the
        // documented NotificationOutboxOptions defaults.
        var configuration = new ConfigurationBuilder().Build();
        services.AddSingleton<IConfiguration>(configuration);
        services.AddLogging();

        // INotificationRepository is registered scoped by the Configuration Database
        // component in production; a no-op stub stands in for it here.
        services.AddScoped<INotificationRepository>(_ => Substitute.For<INotificationRepository>());

        services.AddNotificationService();
        services.AddNotificationOutbox();

        return services.BuildServiceProvider();
    }

    [Fact]
    public void AddNotificationOutbox_RegistersNotificationOutboxOptions_WithDefaults()
    {
        using var provider = BuildProvider();

        var options = provider.GetRequiredService<IOptions<NotificationOutboxOptions>>().Value;

        Assert.NotNull(options);
        Assert.Equal(TimeSpan.FromSeconds(10), options.DispatchInterval);
        Assert.Equal(100, options.DispatchBatchSize);
    }

    [Fact]
    public void AddNotificationOutbox_OptionsSection_IsTheNotificationOutboxConfigPath()
    {
        Assert.Equal(
            "ScadaBridge:NotificationOutbox",
            NotificationOutbox.ServiceCollectionExtensions.OptionsSection);
    }

    [Fact]
    public void AddNotificationOutbox_BindsNotificationOutboxOptions_FromConfiguration()
    {
        var services = new ServiceCollection();
        var configuration = new ConfigurationBuilder()
            .AddInMemoryCollection(new Dictionary<string, string?>
            {
                ["ScadaBridge:NotificationOutbox:DispatchBatchSize"] = "250",
            })
            .Build();
        services.AddSingleton<IConfiguration>(configuration);
        services.AddLogging();
        services.AddScoped<INotificationRepository>(_ => Substitute.For<INotificationRepository>());
        services.AddNotificationService();
        services.AddNotificationOutbox();

        using var provider = services.BuildServiceProvider();
        var options = provider.GetRequiredService<IOptions<NotificationOutboxOptions>>().Value;

        Assert.Equal(250, options.DispatchBatchSize);
    }

    [Fact]
    public void AddNotificationOutbox_RegistersEmailDeliveryAdapter()
    {
        using var provider = BuildProvider();
        using var scope = provider.CreateScope();

        var adapter = scope.ServiceProvider.GetRequiredService<EmailNotificationDeliveryAdapter>();

        Assert.NotNull(adapter);
        Assert.Equal(NotificationType.Email, adapter.Type);
    }

    [Fact]
    public void AddNotificationOutbox_RegistersSmsDeliveryAdapter()
    {
        using var provider = BuildProvider();
        using var scope = provider.CreateScope();

        var adapter = scope.ServiceProvider.GetRequiredService<SmsNotificationDeliveryAdapter>();

        Assert.NotNull(adapter);
        Assert.Equal(NotificationType.Sms, adapter.Type);
    }

    [Fact]
    public void AddNotificationOutbox_RegistersBothAdapters_AsINotificationDeliveryAdapter()
    {
        using var provider = BuildProvider();
        using var scope = provider.CreateScope();

        var adapters = scope.ServiceProvider.GetServices<INotificationDeliveryAdapter>().ToList();

        // Both the Email and SMS channel adapters must resolve from the enumerable so the
        // dispatch sweep can route a notification to the adapter matching its Type.
        Assert.Equal(2, adapters.Count);

        // The set of advertised channel Types is exactly {Email, Sms}.
        Assert.Equal(
            new[] { NotificationType.Email, NotificationType.Sms },
            adapters.Select(a => a.Type).OrderBy(t => t).ToArray());

        // And the concrete adapter implementations are the expected pair.
        Assert.Contains(adapters, a => a is EmailNotificationDeliveryAdapter);
        Assert.Contains(adapters, a => a is SmsNotificationDeliveryAdapter);
    }

    [Fact]
    public void AddNotificationOutbox_RegistersSmsOptions_WithDefaults()
    {
        using var provider = BuildProvider();

        var options = provider.GetRequiredService<IOptions<SmsOptions>>().Value;

        Assert.NotNull(options);
        Assert.Equal(1600, options.MaxMessageLength);
        Assert.Equal(30, options.ConnectionTimeoutSeconds);
    }
}
