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

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

[Trait("Category", "Unit")]
public sealed class AbLegacyAddressTests
{
    /// <summary>Verifies that TryParse accepts valid PCCC addresses.</summary>
    /// <param name="input">The raw PCCC address string to parse.</param>
    /// <param name="letter">The expected file letter.</param>
    /// <param name="file">The expected file number, or null for I/O/S files.</param>
    /// <param name="word">The expected word number.</param>
    /// <param name="bit">The expected bit index, or null if no bit is specified.</param>
    /// <param name="sub">The expected sub-element name, or null if absent.</param>
    [Theory]
    [InlineData("N7:0", "N", 7, 0, null, null)]
    [InlineData("N7:15", "N", 7, 15, null, null)]
    [InlineData("F8:5", "F", 8, 5, null, null)]
    [InlineData("B3:0/0", "B", 3, 0, 0, null)]
    [InlineData("B3:2/7", "B", 3, 2, 7, null)]
    [InlineData("ST9:0", "ST", 9, 0, null, null)]
    [InlineData("L9:3", "L", 9, 3, null, null)]
    [InlineData("I:0/0", "I", null, 0, 0, null)]
    [InlineData("O:1/2", "O", null, 1, 2, null)]
    [InlineData("S:1", "S", null, 1, null, null)]
    [InlineData("T4:0.ACC", "T", 4, 0, null, "ACC")]
    [InlineData("T4:0.PRE", "T", 4, 0, null, "PRE")]
    [InlineData("C5:2.CU", "C", 5, 2, null, "CU")]
    [InlineData("R6:0.LEN", "R", 6, 0, null, "LEN")]
    [InlineData("N7:0/3", "N", 7, 0, 3, null)]
    public void TryParse_accepts_valid_pccc_addresses(string input, string letter, int? file, int word, int? bit, string? sub)
    {
        var a = AbLegacyAddress.TryParse(input);
        a.ShouldNotBeNull();
        a.FileLetter.ShouldBe(letter);
        a.FileNumber.ShouldBe(file);
        a.WordNumber.ShouldBe(word);
        a.BitIndex.ShouldBe(bit);
        a.SubElement.ShouldBe(sub);
    }

    /// <summary>Verifies that TryParse rejects invalid address forms.</summary>
    /// <param name="input">The invalid address string to test, or null.</param>
    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    [InlineData("N7")]              // missing :word
    [InlineData(":0")]              // missing file
    [InlineData("X7:0")]            // unknown file letter
    [InlineData("N7:-1")]           // negative word
    [InlineData("N7:abc")]          // non-numeric word
    [InlineData("N7:0/-1")]         // negative bit
    [InlineData("N7:0/32")]         // bit out of range
    [InlineData("Nabc:0")]          // non-numeric file number
    public void TryParse_rejects_invalid_forms(string? input)
    {
        AbLegacyAddress.TryParse(input).ShouldBeNull();
    }

    /// <summary>Verifies that ToLibplctagName correctly roundtrips addresses.</summary>
    /// <param name="input">The PCCC address string to roundtrip.</param>
    [Theory]
    [InlineData("N7:0")]
    [InlineData("F8:5")]
    [InlineData("B3:0/0")]
    [InlineData("ST9:0")]
    [InlineData("T4:0.ACC")]
    [InlineData("I:0/0")]
    [InlineData("S:1")]
    public void ToLibplctagName_roundtrips(string input)
    {
        var a = AbLegacyAddress.TryParse(input);
        a.ShouldNotBeNull();
        a.ToLibplctagName().ShouldBe(input);
    }

    // ---- Driver.AbLegacy-003: Parser tightening ----

    /// <summary>Verifies that TryParse rejects addresses with both sub-element and bit index.</summary>
    /// <param name="input">The PCCC address string to test.</param>
    [Theory]
    [InlineData("T4:0.ACC/2")]      // sub-element + bit index — never valid in PCCC
    [InlineData("C5:0.PRE/3")]
    public void TryParse_rejects_subelement_plus_bitindex(string input) =>
        AbLegacyAddress.TryParse(input).ShouldBeNull();

    /// <summary>Verifies that TryParse rejects file numbers on system files (I, O, S).</summary>
    /// <param name="input">The PCCC address string to test.</param>
    [Theory]
    [InlineData("I3:0")]            // I is a system file — no file number allowed
    [InlineData("O2:1")]
    [InlineData("S2:1")]
    public void TryParse_rejects_file_number_on_IOS_files(string input) =>
        AbLegacyAddress.TryParse(input).ShouldBeNull();

    /// <summary>Verifies that TryParse rejects sub-element specifications on non-structured files.</summary>
    /// <param name="input">The PCCC address string to test.</param>
    [Theory]
    [InlineData("B3:0.DN")]         // B (bit) file has no structured elements
    [InlineData("N7:0.FOO")]        // N (integer) file has no structured elements
    [InlineData("F8:0.ACC")]        // F (float) file has no structured elements
    [InlineData("L9:0.PRE")]        // L (long) file has no structured elements
    public void TryParse_rejects_subelement_on_non_structured_file(string input) =>
        AbLegacyAddress.TryParse(input).ShouldBeNull();

    /// <summary>Verifies that TryParse accepts sub-element specifications only on T, C, and R files.</summary>
    /// <param name="input">The PCCC address string to test.</param>
    [Theory]
    [InlineData("T4:0.ACC")]        // T, C, R are the only structured-element files
    [InlineData("C5:0.PRE")]
    [InlineData("R6:0.LEN")]
    public void TryParse_accepts_subelement_only_on_TCR_files(string input) =>
        AbLegacyAddress.TryParse(input).ShouldNotBeNull();

    /// <summary>Verifies that TryParse accepts I, O, and S addresses without file numbers.</summary>
    /// <param name="input">The PCCC address string to test.</param>
    [Theory]
    [InlineData("I:0/0")]           // I/O/S without file number are valid
    [InlineData("O:1/2")]
    [InlineData("S:1")]
    public void TryParse_accepts_IOS_without_file_number(string input) =>
        AbLegacyAddress.TryParse(input).ShouldNotBeNull();
}
