using ZB.MOM.WW.GalaxyRepository;

namespace ZB.MOM.WW.GalaxyRepository.Tests;

public sealed class GalaxyDeployNotifierTests
{
    /// <summary>Verifies that a subscriber blocks until a deploy event is published.</summary>
    [Fact]
    public async Task SubscribeAsync_NoLatestEvent_BlocksUntilPublish()
    {
        GalaxyDeployNotifier notifier = new();
        using CancellationTokenSource cts = new();

        IAsyncEnumerator<GalaxyDeployEventInfo> enumerator = notifier
            .SubscribeAsync(cts.Token)
            .GetAsyncEnumerator(cts.Token);

        ValueTask<bool> moveNext = enumerator.MoveNextAsync();
        Assert.False(moveNext.IsCompleted);

        GalaxyDeployEventInfo published = new(
            Sequence: 1,
            ObservedAt: DateTimeOffset.UtcNow,
            TimeOfLastDeploy: DateTimeOffset.UtcNow,
            ObjectCount: 5,
            AttributeCount: 25);
        notifier.Publish(published);

        Assert.True(await moveNext.AsTask().WaitAsync(TimeSpan.FromSeconds(1)));
        Assert.Same(published, enumerator.Current);

        await cts.CancelAsync();
        await enumerator.DisposeAsync();
    }

    /// <summary>Verifies that a subscriber immediately receives a cached latest deploy event.</summary>
    [Fact]
    public async Task SubscribeAsync_WithLatestEvent_BootstrapsImmediately()
    {
        GalaxyDeployNotifier notifier = new();
        GalaxyDeployEventInfo first = new(1, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, 3, 9);
        notifier.Publish(first);

        using CancellationTokenSource cts = new();
        await using IAsyncEnumerator<GalaxyDeployEventInfo> enumerator = notifier
            .SubscribeAsync(cts.Token)
            .GetAsyncEnumerator(cts.Token);

        Assert.True(await enumerator.MoveNextAsync().AsTask().WaitAsync(TimeSpan.FromSeconds(1)));
        Assert.Same(first, enumerator.Current);

        await cts.CancelAsync();
    }

    /// <summary>Verifies that published events fan out to all active subscribers.</summary>
    [Fact]
    public async Task Publish_FansOutToAllSubscribers()
    {
        GalaxyDeployNotifier notifier = new();
        using CancellationTokenSource cts = new();

        await using IAsyncEnumerator<GalaxyDeployEventInfo> a = notifier
            .SubscribeAsync(cts.Token)
            .GetAsyncEnumerator(cts.Token);
        await using IAsyncEnumerator<GalaxyDeployEventInfo> b = notifier
            .SubscribeAsync(cts.Token)
            .GetAsyncEnumerator(cts.Token);

        GalaxyDeployEventInfo info = new(1, DateTimeOffset.UtcNow, null, 0, 0);
        notifier.Publish(info);

        Assert.True(await a.MoveNextAsync().AsTask().WaitAsync(TimeSpan.FromSeconds(1)));
        Assert.True(await b.MoveNextAsync().AsTask().WaitAsync(TimeSpan.FromSeconds(1)));
        Assert.Same(info, a.Current);
        Assert.Same(info, b.Current);

        await cts.CancelAsync();
    }

    /// <summary>Verifies that the Latest property tracks the most recently published event.</summary>
    [Fact]
    public void Latest_TracksMostRecentPublish()
    {
        GalaxyDeployNotifier notifier = new();
        Assert.Null(notifier.Latest);

        GalaxyDeployEventInfo first = new(1, DateTimeOffset.UtcNow, null, 0, 0);
        GalaxyDeployEventInfo second = new(2, DateTimeOffset.UtcNow, null, 0, 0);
        notifier.Publish(first);
        notifier.Publish(second);

        Assert.Same(second, notifier.Latest);
    }
}
