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

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

[Trait("Category", "Unit")]
public sealed class TwinCATSymbolPathTests
{
    /// <summary>Verifies that a single segment global variable parses correctly.</summary>
    [Fact]
    public void Single_segment_global_variable_parses()
    {
        var p = TwinCATSymbolPath.TryParse("Counter");
        p.ShouldNotBeNull();
        p.Segments.Single().Name.ShouldBe("Counter");
        p.ToAdsSymbolName().ShouldBe("Counter");
    }

    /// <summary>Verifies that a POU dot-separated variable parses correctly.</summary>
    [Fact]
    public void POU_dot_variable_parses()
    {
        var p = TwinCATSymbolPath.TryParse("MAIN.bStart");
        p.ShouldNotBeNull();
        p.Segments.Select(s => s.Name).ShouldBe(["MAIN", "bStart"]);
        p.ToAdsSymbolName().ShouldBe("MAIN.bStart");
    }

    /// <summary>Verifies that a GVL reference parses correctly.</summary>
    [Fact]
    public void GVL_reference_parses()
    {
        var p = TwinCATSymbolPath.TryParse("GVL.Counter");
        p.ShouldNotBeNull();
        p.Segments.Select(s => s.Name).ShouldBe(["GVL", "Counter"]);
        p.ToAdsSymbolName().ShouldBe("GVL.Counter");
    }

    /// <summary>Verifies that structured member access is split into segments.</summary>
    [Fact]
    public void Structured_member_access_splits()
    {
        var p = TwinCATSymbolPath.TryParse("Motor1.Status.Running");
        p.ShouldNotBeNull();
        p.Segments.Select(s => s.Name).ShouldBe(["Motor1", "Status", "Running"]);
    }

    /// <summary>Verifies that array subscripts parse correctly.</summary>
    [Fact]
    public void Array_subscript_parses()
    {
        var p = TwinCATSymbolPath.TryParse("Data[5]");
        p.ShouldNotBeNull();
        p.Segments.Single().Subscripts.ShouldBe([5]);
        p.ToAdsSymbolName().ShouldBe("Data[5]");
    }

    /// <summary>Verifies that multi-dimensional array subscripts parse correctly.</summary>
    [Fact]
    public void Multi_dim_array_subscript_parses()
    {
        var p = TwinCATSymbolPath.TryParse("Matrix[1,2]");
        p.ShouldNotBeNull();
        p.Segments.Single().Subscripts.ShouldBe([1, 2]);
    }

    /// <summary>Verifies that bit access is captured as a bit index.</summary>
    [Fact]
    public void Bit_access_captured_as_bit_index()
    {
        var p = TwinCATSymbolPath.TryParse("Flags.3");
        p.ShouldNotBeNull();
        p.Segments.Single().Name.ShouldBe("Flags");
        p.BitIndex.ShouldBe(3);
        p.ToAdsSymbolName().ShouldBe("Flags.3");
    }

    /// <summary>Verifies that bit access works after a member path.</summary>
    [Fact]
    public void Bit_access_after_member_path()
    {
        var p = TwinCATSymbolPath.TryParse("GVL.Status.7");
        p.ShouldNotBeNull();
        p.Segments.Select(s => s.Name).ShouldBe(["GVL", "Status"]);
        p.BitIndex.ShouldBe(7);
    }

    /// <summary>Verifies that combined scope, member, subscript, and bit access parses correctly.</summary>
    [Fact]
    public void Combined_scope_member_subscript_bit()
    {
        var p = TwinCATSymbolPath.TryParse("MAIN.Motors[0].Status.5");
        p.ShouldNotBeNull();
        p.Segments.Select(s => s.Name).ShouldBe(["MAIN", "Motors", "Status"]);
        p.Segments[1].Subscripts.ShouldBe([0]);
        p.BitIndex.ShouldBe(5);
        p.ToAdsSymbolName().ShouldBe("MAIN.Motors[0].Status.5");
    }

    /// <summary>Verifies that invalid symbol path shapes return null.</summary>
    /// <param name="input">The invalid symbol path input to test.</param>
    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    [InlineData(".Motor")]              // leading dot
    [InlineData("Motor.")]              // trailing dot
    [InlineData("Motor.[0]")]           // empty segment
    [InlineData("1bad")]                // ident starts with digit
    [InlineData("Bad Name")]            // space in ident
    [InlineData("Motor[]")]             // empty subscript
    [InlineData("Motor[-1]")]           // negative subscript
    [InlineData("Motor[a]")]            // non-numeric subscript
    [InlineData("Motor[")]              // unbalanced bracket
    [InlineData("Flags.32")]            // bit out of range (treated as ident → invalid shape)
    public void Invalid_shapes_return_null(string? input)
    {
        TwinCATSymbolPath.TryParse(input).ShouldBeNull();
    }

    /// <summary>Verifies that underscore-prefixed identifiers are accepted.</summary>
    [Fact]
    public void Underscore_prefix_idents_accepted()
    {
        TwinCATSymbolPath.TryParse("_internal_var")!.Segments.Single().Name.ShouldBe("_internal_var");
    }

    /// <summary>Verifies that ToAdsSymbolName roundtrips correctly for various symbol formats.</summary>
    [Fact]
    public void ToAdsSymbolName_roundtrips()
    {
        var cases = new[]
        {
            "Counter",
            "MAIN.bStart",
            "GVL.Counter",
            "Motor1.Status.Running",
            "Data[5]",
            "Matrix[1,2]",
            "Flags.3",
            "MAIN.Motors[0].Status.5",
        };
        foreach (var c in cases)
        {
            var parsed = TwinCATSymbolPath.TryParse(c);
            parsed.ShouldNotBeNull(c);
            parsed.ToAdsSymbolName().ShouldBe(c);
        }
    }
}
