using Microsoft.Extensions.Logging.Abstractions;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Scripting;
using ZB.MOM.WW.ScadaBridge.Host.Services;

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

/// <summary>
/// #05-T14: unit tests for the in-process script-artifact change bus — delivery,
/// unsubscribe, and the swallow-and-log guard that keeps a faulting subscriber from
/// faulting the producer.
/// </summary>
public sealed class InProcessScriptArtifactChangeBusTests
{
    private static InProcessScriptArtifactChangeBus NewBus() =>
        new(NullLogger<InProcessScriptArtifactChangeBus>.Instance);

    private static ScriptArtifactsChanged Sample(string kind = ScriptArtifactKinds.ApiMethod) =>
        new(kind, new[] { "M1" }, "Management", DateTimeOffset.UnixEpoch);

    [Fact]
    public void Publish_delivers_to_all_current_subscribers()
    {
        var bus = NewBus();
        var a = new List<ScriptArtifactsChanged>();
        var b = new List<ScriptArtifactsChanged>();
        bus.Subscribe(a.Add);
        bus.Subscribe(b.Add);

        var n = Sample();
        bus.Publish(n);

        Assert.Same(n, Assert.Single(a));
        Assert.Same(n, Assert.Single(b));
    }

    [Fact]
    public void Disposing_subscription_stops_delivery()
    {
        var bus = NewBus();
        var received = new List<ScriptArtifactsChanged>();
        var token = bus.Subscribe(received.Add);

        bus.Publish(Sample());
        token.Dispose();
        bus.Publish(Sample());

        Assert.Single(received); // only the pre-dispose publish landed.
    }

    [Fact]
    public void Dispose_is_idempotent()
    {
        var bus = NewBus();
        var received = new List<ScriptArtifactsChanged>();
        var token = bus.Subscribe(received.Add);

        token.Dispose();
        token.Dispose(); // must not throw nor corrupt the handler list.

        bus.Publish(Sample());
        Assert.Empty(received);
    }

    [Fact]
    public void A_faulting_subscriber_does_not_prevent_delivery_to_others_nor_throw()
    {
        var bus = NewBus();
        var good = new List<ScriptArtifactsChanged>();
        bus.Subscribe(_ => throw new InvalidOperationException("bad consumer"));
        bus.Subscribe(good.Add);

        // Publish must not propagate the subscriber fault...
        var ex = Record.Exception(() => bus.Publish(Sample()));
        Assert.Null(ex);
        // ...and the well-behaved subscriber still receives it.
        Assert.Single(good);
    }

    [Fact]
    public void Publish_with_no_subscribers_is_a_noop()
    {
        var bus = NewBus();
        Assert.Null(Record.Exception(() => bus.Publish(Sample())));
    }
}
