using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;

namespace ZB.MOM.WW.ScadaBridge.Commons.Tests.Interfaces.Services;

/// <summary>
/// Tests for <see cref="ExternalCallResult"/>, in particular the Commons-021
/// thread-safe lazy parse of <c>Response</c>. The pre-fix implementation used
/// two mutable fields (<c>_response</c>/<c>_responseParsed</c>) with no
/// synchronization, so concurrent readers could each construct a fresh
/// <c>DynamicJsonElement</c> and one would overwrite the other. The fix moves
/// the parse onto a <c>Lazy&lt;dynamic?&gt;</c> with
/// <c>LazyThreadSafetyMode.ExecutionAndPublication</c> (the default), which
/// guarantees one parse and one shared result for all readers.
/// </summary>
public class ExternalCallResultTests
{
    [Fact]
    public void Response_NullOrEmptyJson_ReturnsNull()
    {
        var withNull = new ExternalCallResult(Success: true, ResponseJson: null, ErrorMessage: null);
        var withEmpty = new ExternalCallResult(Success: true, ResponseJson: string.Empty, ErrorMessage: null);

        Assert.Null(withNull.Response);
        Assert.Null(withEmpty.Response);
    }

    [Fact]
    public void Response_ParsesJsonIntoDynamicElement()
    {
        var result = new ExternalCallResult(Success: true, ResponseJson: "{\"answer\": 42}", ErrorMessage: null);

        // dynamic property access is the production usage pattern.
        dynamic? response = result.Response;
        Assert.NotNull(response);
        int answer = (int)response!.answer;
        Assert.Equal(42, answer);
    }

    /// <summary>
    /// Commons-021: concurrent readers must observe the same parsed instance
    /// (a `Lazy&lt;T&gt;` invariant). Under the pre-fix code two threads could
    /// both produce a fresh `DynamicJsonElement` and one would win the race —
    /// `ReferenceEquals` would then occasionally fail. With the fix every
    /// reader observes the single Lazy-published value, so the assertion
    /// holds for every pair of observers.
    /// </summary>
    [Fact]
    public void Response_ConcurrentReads_ReturnSameInstance()
    {
        // A larger payload makes the parse window wider so the race, if
        // present, is more likely to fire. The same property — single
        // published instance — must hold for any payload, though.
        var json = "{\"items\":[{\"name\":\"a\"},{\"name\":\"b\"},{\"name\":\"c\"}],\"count\":3}";
        var result = new ExternalCallResult(Success: true, ResponseJson: json, ErrorMessage: null);

        const int observerCount = 64;
        var barrier = new Barrier(observerCount);
        var observed = new object?[observerCount];

        Parallel.For(0, observerCount, i =>
        {
            // Force all observers to call `Response` at the same instant so
            // they collide on the lazy parse rather than each finding it
            // already-published.
            barrier.SignalAndWait();
            observed[i] = result.Response;
        });

        var first = observed[0];
        Assert.NotNull(first);
        for (var i = 1; i < observerCount; i++)
        {
            Assert.Same(first, observed[i]);
        }
    }
}
