using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

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

/// <summary>
///     End-to-end round-trip against the <c>rtu_over_tcp.json</c> pymodbus profile (or a real
///     serial→Ethernet Modbus gateway when <c>MODBUS_RTU_SIM_ENDPOINT</c> points at one), driving the
///     full <see cref="ModbusDriver"/> + real <see cref="ModbusRtuOverTcpTransport"/> stack with
///     <see cref="ModbusTransportMode.RtuOverTcp"/>. Proves the driver reads a seeded holding register
///     and writes+reads-back a scratch register when the wire carries RTU framing
///     (<c>[addr][PDU][CRC-16]</c>, no MBAP header) rather than Modbus/TCP.
/// </summary>
/// <remarks>
///     pymodbus 3.13's simulator honors <c>framer=rtu</c> on a TCP server — confirmed on the wire
///     (controller spike, 2026-07-24): a raw RTU FC03 read of HR[5] returned
///     <c>01 03 02 00 05 78 47</c>, a pure RTU frame with no MBAP header (data 0x0005 = 5). So the
///     fixture is the plain pymodbus simulator with framer/port swapped — no stdlib fallback server.
/// </remarks>
[Collection(ModbusRtuOverTcpCollection.Name)]
[Trait("Category", "Integration")]
[Trait("Device", "RtuOverTcp")]
public sealed class ModbusRtuOverTcpTests(ModbusRtuOverTcpFixture sim)
{
    /// <summary>Reads a seeded holding register (HR[5]=5) over RTU-over-TCP framing.</summary>
    [Fact]
    public async Task Reads_a_seeded_holding_register_over_rtu_framing()
    {
        if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason);
        var opts = new ModbusDriverOptions
        {
            Host = sim.Host,
            Port = sim.Port,
            UnitId = 1,
            Transport = ModbusTransportMode.RtuOverTcp,
            Timeout = TimeSpan.FromSeconds(2),
            Probe = new ModbusProbeOptions { Enabled = false },
            RawTags = ModbusRawTags.Entries([
                new ModbusTagDefinition(
                    Name: "HR5",
                    Region: ModbusRegion.HoldingRegisters,
                    Address: 5,
                    DataType: ModbusDataType.UInt16,
                    Writable: false),
            ]),
        };
        await using var driver = new ModbusDriver(opts, driverInstanceId: "modbus-rtu-int");
        await driver.InitializeAsync(driverConfigJson: "{}", TestContext.Current.CancellationToken);

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

        results.Count.ShouldBe(1);
        results[0].StatusCode.ShouldBe(0u);       // Good
        results[0].Value.ShouldBe((ushort)5);     // HR[5] seeded = address-as-value
    }

    /// <summary>Writes then reads back a scratch holding register (HR[200]) over RTU-over-TCP framing.</summary>
    [Fact]
    public async Task Writes_and_reads_back_a_holding_register_over_rtu_framing()
    {
        if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason);
        var opts = new ModbusDriverOptions
        {
            Host = sim.Host,
            Port = sim.Port,
            UnitId = 1,
            Transport = ModbusTransportMode.RtuOverTcp,
            Timeout = TimeSpan.FromSeconds(2),
            Probe = new ModbusProbeOptions { Enabled = false },
            RawTags = ModbusRawTags.Entries([
                new ModbusTagDefinition(
                    Name: "HR200",
                    Region: ModbusRegion.HoldingRegisters,
                    Address: 200,
                    DataType: ModbusDataType.UInt16,
                    Writable: true),
            ]),
        };
        await using var driver = new ModbusDriver(opts, driverInstanceId: "modbus-rtu-int-w");
        await driver.InitializeAsync(driverConfigJson: "{}", TestContext.Current.CancellationToken);

        var writes = await driver.WriteAsync(
            [new WriteRequest("HR200", (ushort)4242)],
            TestContext.Current.CancellationToken);
        writes.Count.ShouldBe(1);
        writes[0].StatusCode.ShouldBe(0u);

        var back = await driver.ReadAsync(["HR200"], TestContext.Current.CancellationToken);
        back.Count.ShouldBe(1);
        back[0].StatusCode.ShouldBe(0u);
        back[0].Value.ShouldBe((ushort)4242);
    }
}
