using Shouldly;
using Xunit;

namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests.S7;

/// <summary>
///     Verifies the Siemens S7 big-endian (<c>ABCD</c>) word-order default for Float32 and
///     Int32 against the <c>s7_1500.json</c> pymodbus profile. S7's native CPU types are
///     big-endian end-to-end, so <c>MB_SERVER</c> places the high word at the lower register
///     address — <b>opposite</b> of DL260's CDAB. The driver's S7-family tag config must
///     therefore default to <see cref="ModbusByteOrder.BigEndian"/>; selecting
///     <see cref="ModbusByteOrder.WordSwap"/> against an S7 would decode garbage.
/// </summary>
[Collection(ModbusSimulatorCollection.Name)]
[Trait("Category", "Integration")]
[Trait("Device", "S7")]
public sealed class S7_ByteOrderTests(ModbusSimulatorFixture sim)
{
    /// <summary>Verifies that S7 Float32 with ABCD byte order correctly decodes 1.5f from HR100.</summary>
    [Fact]
    public async Task S7_Float32_ABCD_decodes_1_5f_from_HR100()
    {
        if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason);
        if (!string.Equals(Environment.GetEnvironmentVariable("MODBUS_SIM_PROFILE"), "s7_1500",
                StringComparison.OrdinalIgnoreCase))
        {
            Assert.Skip("MODBUS_SIM_PROFILE != s7_1500 — skipping (s7_1500 profile is the only one seeding HR[100..101] ABCD).");
        }

        var options = new ModbusDriverOptions
        {
            Host = sim.Host,
            Port = sim.Port,
            UnitId = 1,
            Timeout = TimeSpan.FromSeconds(2),
            RawTags = ModbusRawTags.Entries([
                new ModbusTagDefinition("S7_Float_ABCD",
                    ModbusRegion.HoldingRegisters, Address: 100,
                    DataType: ModbusDataType.Float32, Writable: false,
                    ByteOrder: ModbusByteOrder.BigEndian),
                // Control: same address with WordSwap should decode garbage — proves the
                // two code paths diverge on S7 wire bytes.
                new ModbusTagDefinition("S7_Float_CDAB_control",
                    ModbusRegion.HoldingRegisters, Address: 100,
                    DataType: ModbusDataType.Float32, Writable: false,
                    ByteOrder: ModbusByteOrder.WordSwap),
            ]),
            Probe = new ModbusProbeOptions { Enabled = false },
        };
        await using var driver = new ModbusDriver(options, driverInstanceId: "s7-float-abcd");
        await driver.InitializeAsync("{}", TestContext.Current.CancellationToken);

        var results = await driver.ReadAsync(
            ["S7_Float_ABCD", "S7_Float_CDAB_control"],
            TestContext.Current.CancellationToken);

        results[0].StatusCode.ShouldBe(0u);
        results[0].Value.ShouldBe(1.5f, "S7 MB_SERVER stores Float32 in ABCD word order; BigEndian decode returns 1.5f");

        results[1].StatusCode.ShouldBe(0u);
        results[1].Value.ShouldNotBe(1.5f, "applying CDAB swap to S7 ABCD bytes must produce a different value — confirms the flag is not a no-op and S7 profile default must be BigEndian");
    }

    /// <summary>Verifies that S7 Int32 with ABCD byte order correctly decodes 0x12345678 from HR300.</summary>
    [Fact]
    public async Task S7_Int32_ABCD_decodes_0x12345678_from_HR300()
    {
        if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason);
        if (!string.Equals(Environment.GetEnvironmentVariable("MODBUS_SIM_PROFILE"), "s7_1500",
                StringComparison.OrdinalIgnoreCase))
        {
            Assert.Skip("MODBUS_SIM_PROFILE != s7_1500 — skipping.");
        }

        var options = new ModbusDriverOptions
        {
            Host = sim.Host,
            Port = sim.Port,
            UnitId = 1,
            Timeout = TimeSpan.FromSeconds(2),
            RawTags = ModbusRawTags.Entries([
                new ModbusTagDefinition("S7_Int32_ABCD",
                    ModbusRegion.HoldingRegisters, Address: 300,
                    DataType: ModbusDataType.Int32, Writable: false,
                    ByteOrder: ModbusByteOrder.BigEndian),
            ]),
            Probe = new ModbusProbeOptions { Enabled = false },
        };
        await using var driver = new ModbusDriver(options, driverInstanceId: "s7-int-abcd");
        await driver.InitializeAsync("{}", TestContext.Current.CancellationToken);

        var results = await driver.ReadAsync(["S7_Int32_ABCD"], TestContext.Current.CancellationToken);
        results[0].StatusCode.ShouldBe(0u);
        results[0].Value.ShouldBe(0x12345678,
            "S7 Int32 stored as HR[300]=0x1234, HR[301]=0x5678 with ABCD order decodes to 0x12345678 — DL260 would store the reverse order");
    }

    /// <summary>Verifies that S7 DB1 fingerprint marker reads 0xABCD from HR0.</summary>
    [Fact]
    public async Task S7_DB1_fingerprint_marker_at_HR0_reads_0xABCD()
    {
        if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason);
        if (!string.Equals(Environment.GetEnvironmentVariable("MODBUS_SIM_PROFILE"), "s7_1500",
                StringComparison.OrdinalIgnoreCase))
        {
            Assert.Skip("MODBUS_SIM_PROFILE != s7_1500 — skipping.");
        }

        // Real-world MB_SERVER deployments typically reserve DB1.DBW0 as a fingerprint so
        // clients can verify they're pointing at the right DB (protects against typos in
        // the MB_SERVER.MB_HOLD_REG.DB_number parameter). 0xABCD is the convention.
        var options = new ModbusDriverOptions
        {
            Host = sim.Host,
            Port = sim.Port,
            UnitId = 1,
            Timeout = TimeSpan.FromSeconds(2),
            RawTags = ModbusRawTags.Entries([
                new ModbusTagDefinition("S7_Fingerprint",
                    ModbusRegion.HoldingRegisters, Address: 0,
                    DataType: ModbusDataType.UInt16, Writable: false),
            ]),
            Probe = new ModbusProbeOptions { Enabled = false },
        };
        await using var driver = new ModbusDriver(options, driverInstanceId: "s7-fingerprint");
        await driver.InitializeAsync("{}", TestContext.Current.CancellationToken);

        var results = await driver.ReadAsync(["S7_Fingerprint"], TestContext.Current.CancellationToken);
        results[0].StatusCode.ShouldBe(0u);
        results[0].Value.ShouldBe((ushort)0xABCD);
    }
}
