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

namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.Addressing.Tests;

[Trait("Category", "Unit")]
public sealed class ModbusModiconAddressTests
{
    /// <summary>Verifies that valid Modicon addresses parse correctly to region and offset.</summary>
    /// <param name="address">The Modicon address string to parse.</param>
    /// <param name="expectedRegion">The expected Modbus region.</param>
    /// <param name="expectedOffset">The expected PDU offset.</param>
    [Theory]
    // 5-digit form, one per region. Trailing 4 digits = 1-based register; PDU offset is one less.
    [InlineData("00001", ModbusRegion.Coils,            (ushort)0)]
    [InlineData("09999", ModbusRegion.Coils,            (ushort)9998)]
    [InlineData("10001", ModbusRegion.DiscreteInputs,   (ushort)0)]
    [InlineData("19999", ModbusRegion.DiscreteInputs,   (ushort)9998)]
    [InlineData("30001", ModbusRegion.InputRegisters,   (ushort)0)]
    [InlineData("39999", ModbusRegion.InputRegisters,   (ushort)9998)]
    [InlineData("40001", ModbusRegion.HoldingRegisters, (ushort)0)]
    [InlineData("49999", ModbusRegion.HoldingRegisters, (ushort)9998)]
    // 6-digit form unlocks the full 16-bit address space — 1..65536 → PDU 0..65535.
    [InlineData("400001", ModbusRegion.HoldingRegisters, (ushort)0)]
    [InlineData("410000", ModbusRegion.HoldingRegisters, (ushort)9999)]
    [InlineData("465536", ModbusRegion.HoldingRegisters, (ushort)65535)]
    [InlineData("000001", ModbusRegion.Coils,            (ushort)0)]
    [InlineData("100001", ModbusRegion.DiscreteInputs,   (ushort)0)]
    [InlineData("365536", ModbusRegion.InputRegisters,   (ushort)65535)]
    public void Parse_Valid(string address, ModbusRegion expectedRegion, ushort expectedOffset)
    {
        var (region, offset) = ModbusModiconAddress.Parse(address);
        region.ShouldBe(expectedRegion);
        offset.ShouldBe(expectedOffset);
    }

    /// <summary>Verifies that invalid addresses produce diagnostic error messages.</summary>
    /// <param name="address">The invalid address string to test.</param>
    /// <param name="fragment">The expected diagnostic fragment in the exception message.</param>
    [Theory]
    [InlineData("",        "null or empty")]
    [InlineData("   ",     "null or empty")]
    [InlineData("4001",    "5 or 6 digits")]                  // 4 chars
    [InlineData("4000001", "5 or 6 digits")]                  // 7 chars
    [InlineData("4000A",   "only digits")]                    // letter in body
    [InlineData("X0001",   "only digits")]                    // letter leading
    [InlineData("20001",   "leading digit must be 0/1/3/4")]  // region 2 doesn't exist
    [InlineData("50001",   "leading digit must be 0/1/3/4")]
    [InlineData("90001",   "leading digit must be 0/1/3/4")]
    [InlineData("40000",   "must be >= 1")]                   // 0-based register number
    [InlineData("400000",  "must be >= 1")]                   // 6-digit zero
    public void Parse_Invalid_Surfaces_Diagnostic(string address, string fragment)
    {
        Should.Throw<FormatException>(() => ModbusModiconAddress.Parse(address))
            .Message.ShouldContain(fragment, Case.Insensitive);
    }

    /// <summary>Verifies that TryParse returns false with a diagnostic error on invalid input.</summary>
    [Fact]
    public void TryParse_Returns_False_With_Diagnostic_On_Invalid()
    {
        var ok = ModbusModiconAddress.TryParse("not-an-address", out _, out _, out var error);
        ok.ShouldBeFalse();
        error.ShouldNotBeNull();
    }

    /// <summary>Verifies that TryParse returns true with null error on valid input.</summary>
    [Fact]
    public void TryParse_Returns_True_With_Null_Error_On_Valid()
    {
        var ok = ModbusModiconAddress.TryParse("40001", out var region, out var offset, out var error);
        ok.ShouldBeTrue();
        region.ShouldBe(ModbusRegion.HoldingRegisters);
        offset.ShouldBe((ushort)0);
        error.ShouldBeNull();
    }

    /// <summary>Verifies that TryParse handles null input gracefully.</summary>
    [Fact]
    public void TryParse_Handles_Null()
    {
        ModbusModiconAddress.TryParse(null, out _, out _, out var error).ShouldBeFalse();
        error.ShouldNotBeNull();
    }

    /// <summary>Verifies that TryParse trims leading and trailing whitespace.</summary>
    [Fact]
    public void TryParse_Trims_Whitespace()
    {
        // Tag spreadsheets often arrive with stray padding; the parser tolerates it rather than
        // forcing every importer to trim — but stays strict on the 5/6-digit length once trimmed.
        ModbusModiconAddress.TryParse("  40001  ", out var region, out var offset, out _).ShouldBeTrue();
        region.ShouldBe(ModbusRegion.HoldingRegisters);
        offset.ShouldBe((ushort)0);
    }
}
