using NSubstitute;
using ZB.MOM.WW.ScadaBridge.CentralUI.ScriptAnalysis;
using ZB.MOM.WW.ScadaBridge.Commons.Types;

namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.ScriptAnalysis;

/// <summary>
/// Deferred-work #14 (full fidelity): the Test-Run sandbox routes value-equality
/// <c>WaitAsync</c>/<c>WaitForAsync</c> to the bound instance via
/// <see cref="ISandboxInstanceGateway"/>; predicate overloads and unbound runs still
/// throw a clearly-labelled <see cref="ScriptSandboxException"/>.
/// </summary>
public class SandboxWaitForAttributeTests
{
    private static SandboxAttributeAccessor BoundAccessor(ISandboxInstanceGateway gateway) =>
        new(new SandboxInstanceContext(gateway), prefix: string.Empty);

    [Fact]
    public async Task WaitAsync_ValueForm_RoutesThroughGateway_AndReturnsMatched()
    {
        var gateway = Substitute.For<ISandboxInstanceGateway>();
        gateway.WaitForAttributeAsync("Flag", Arg.Any<string?>(), Arg.Any<TimeSpan>(), false, Arg.Any<CancellationToken>())
            .Returns(new WaitResult(true, true, "Good", false));
        var accessor = BoundAccessor(gateway);

        var matched = await accessor.WaitAsync("Flag", true, TimeSpan.FromSeconds(5));

        Assert.True(matched);
        // The target is codec-encoded and the resolved (root-scope) name is passed through.
        await gateway.Received(1).WaitForAttributeAsync(
            "Flag", AttributeValueCodec.Encode(true), TimeSpan.FromSeconds(5), false, Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task WaitForAsync_ValueForm_PassesRequireGoodQuality_AndReturnsFullResult()
    {
        var gateway = Substitute.For<ISandboxInstanceGateway>();
        gateway.WaitForAttributeAsync("Done", Arg.Any<string?>(), Arg.Any<TimeSpan>(), true, Arg.Any<CancellationToken>())
            .Returns(new WaitResult(true, 7, "Good", false));
        var accessor = BoundAccessor(gateway);

        var result = await accessor.WaitForAsync("Done", 7, TimeSpan.FromSeconds(3), requireGoodQuality: true);

        Assert.True(result.Matched);
        Assert.Equal(7, result.Value);
        Assert.Equal("Good", result.Quality);
        await gateway.Received(1).WaitForAttributeAsync(
            "Done", AttributeValueCodec.Encode(7), TimeSpan.FromSeconds(3), true, Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task WaitAsync_PredicateForm_Throws()
    {
        var accessor = BoundAccessor(Substitute.For<ISandboxInstanceGateway>());

        var ex = await Assert.ThrowsAsync<ScriptSandboxException>(
            () => accessor.WaitAsync("Done", v => v != null, TimeSpan.FromSeconds(1)));
        Assert.Contains("predicate", ex.Message, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public async Task WaitForAsync_PredicateForm_Throws()
    {
        var accessor = BoundAccessor(Substitute.For<ISandboxInstanceGateway>());

        await Assert.ThrowsAsync<ScriptSandboxException>(
            () => accessor.WaitForAsync("Done", v => v != null, TimeSpan.FromSeconds(1)));
    }

    [Fact]
    public async Task WaitAsync_ValueForm_UnboundInstance_Throws()
    {
        // No gateway bound → the sandbox has no instance to route to.
        var accessor = new SandboxAttributeAccessor(new SandboxInstanceContext(), prefix: string.Empty);

        var ex = await Assert.ThrowsAsync<ScriptSandboxException>(
            () => accessor.WaitAsync("Flag", true, TimeSpan.FromSeconds(1)));
        Assert.Contains("deployed instance", ex.Message, StringComparison.OrdinalIgnoreCase);
    }
}
