using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.AbCip;

namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests;

/// <summary>
///     ParseOptions timeout hardening + the array-shape round-trip through the v3 tag mapper. Under v3 the
///     factory no longer builds typed tag definitions (it carries authored <c>RawTags</c> blobs verbatim);
///     the array/member shape survives the TagConfig round-trip in
///     <see cref="AbCipTagDefinitionFactory.FromTagConfig"/> instead. These tests assert the array shape
///     survives mapping for both top-level tags and declared UDT members, and that omitting the fields keeps
///     the scalar default.
/// </summary>
[Trait("Category", "Unit")]
public sealed class AbCipFactoryArrayTagTests
{
    private const string Raw = "line1/eq/tag";

    /// <summary>
    ///     A non-positive <c>TimeoutMs</c> clamps to the 2 s default rather than reaching libplctag's
    ///     <c>Tag.Timeout</c> setter (which throws <see cref="ArgumentOutOfRangeException"/> for a
    ///     non-positive value, faulting every read/write). Driver timeout hardening.
    /// </summary>
    [Theory]
    [InlineData(0)]
    [InlineData(-1)]
    public void ParseOptions_clamps_non_positive_TimeoutMs_to_the_default(int timeoutMs)
    {
        var json = $$$"""{"TimeoutMs":{{{timeoutMs}}},"Probe":{"TimeoutMs":{{{timeoutMs}}}}}""";

        var opts = AbCipDriverFactoryExtensions.ParseOptions("drv-1", json);

        opts.Timeout.ShouldBe(TimeSpan.FromMilliseconds(2_000));
        opts.Timeout.ShouldBeGreaterThan(TimeSpan.Zero);
        opts.Probe.Timeout.ShouldBe(TimeSpan.FromMilliseconds(2_000));
    }

    /// <summary>A positive <c>TimeoutMs</c> is honoured verbatim.</summary>
    [Fact]
    public void ParseOptions_honours_a_positive_TimeoutMs()
    {
        var opts = AbCipDriverFactoryExtensions.ParseOptions("drv-1", """{"TimeoutMs":500}""");
        opts.Timeout.ShouldBe(TimeSpan.FromMilliseconds(500));
    }

    /// <summary>A TagConfig declaring isArray/arrayLength maps to an array definition.</summary>
    [Fact]
    public void FromTagConfig_threads_isArray_and_arrayLength_into_tag_definition()
    {
        const string tagConfig = """{"tagPath":"Setpoints","dataType":"Real","isArray":true,"arrayLength":4}""";

        AbCipTagDefinitionFactory.FromTagConfig(tagConfig, Raw, out var tag).ShouldBeTrue();

        tag.IsArray.ShouldBeTrue();
        tag.ElementCount.ShouldBe(4);
    }

    /// <summary>A declared UDT member declaring isArray/arrayLength maps to an array member.</summary>
    [Fact]
    public void FromTagConfig_threads_isArray_and_arrayLength_into_structure_member()
    {
        const string tagConfig = """
            {"tagPath":"Motor","dataType":"Structure",
             "members":[{"name":"Setpoints","dataType":"Real","isArray":true,"arrayLength":4}]}
            """;

        AbCipTagDefinitionFactory.FromTagConfig(tagConfig, Raw, out var tag).ShouldBeTrue();

        var member = tag.Members!.Single();
        member.IsArray.ShouldBeTrue();
        member.ElementCount.ShouldBe(4);
    }

    /// <summary>A TagConfig without array fields stays scalar.</summary>
    [Fact]
    public void FromTagConfig_defaults_to_scalar_when_array_fields_absent()
    {
        const string tagConfig = """{"tagPath":"Speed","dataType":"DInt"}""";

        AbCipTagDefinitionFactory.FromTagConfig(tagConfig, Raw, out var tag).ShouldBeTrue();

        tag.IsArray.ShouldBeFalse();
        tag.ElementCount.ShouldBe(1);
    }

    /// <summary>A non-positive arrayLength falls back to the scalar count of 1 (canonical rule: scalar).</summary>
    [Fact]
    public void FromTagConfig_guards_against_non_positive_arrayLength()
    {
        const string tagConfig = """{"tagPath":"Speed","dataType":"DInt","isArray":true,"arrayLength":0}""";

        AbCipTagDefinitionFactory.FromTagConfig(tagConfig, Raw, out var tag).ShouldBeTrue();

        // arrayLength <= 0 with isArray:true is degenerate — canonical rule collapses to scalar.
        tag.ElementCount.ShouldBe(1);
        tag.IsArray.ShouldBeFalse();
    }
}
