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

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

/// <summary>
///     Gitea #516 — <see cref="ModbusDriver.InitializeAsync"/> used to serve the options its CONSTRUCTOR
///     captured and never look at the <c>driverConfigJson</c> it was handed, so an operator's config edit
///     was silently discarded while the deployment still sealed green.
///     <para><b>This test passes a CHANGED config on purpose.</b> Every pre-existing reinit test in this
///     suite passes <c>"{}"</c> — exactly the input the guarded re-parser treats as "keep the constructor
///     options" — so those tests were blind to the defect by construction.</para>
/// </summary>
[Trait("Category", "Unit")]
public sealed class ModbusReinitConfigAdoptionTests
{
    /// <summary>The transport factory receives the options the driver actually decided to use, so it is a
    /// direct read of which config won — no log-string matching.</summary>
    [Fact]
    public async Task Reinitialize_with_a_changed_host_rebuilds_the_transport_against_the_new_host()
    {
        var seen = new List<string>();
        var options = ModbusDriverFactoryExtensions.ParseOptions("m1", """{"host":"10.0.0.1","port":502}""");
        var driver = new ModbusDriver(options, "m1", opts =>
        {
            seen.Add($"{opts.Host}:{opts.Port}");
            return new NeverConnectingTransport();
        });

        try { await driver.InitializeAsync("""{"host":"10.0.0.1","port":502}""", CancellationToken.None); }
        catch { /* connect failure is expected and irrelevant */ }

        try { await driver.ReinitializeAsync("""{"host":"10.0.0.99","port":1502}""", CancellationToken.None); }
        catch { /* ditto */ }

        seen.Count.ShouldBeGreaterThanOrEqualTo(2);
        seen[^1].ShouldBe("10.0.0.99:1502",
            "ModbusDriver kept its constructor-supplied host after a reinitialize with a changed config (#516)");
    }

    /// <summary>A placeholder / empty document must still keep the constructor options — the guard exists so
    /// the many lifecycle tests that pass <c>"{}"</c> keep meaning what they meant.</summary>
    [Fact]
    public async Task Reinitialize_with_an_empty_document_keeps_the_constructor_options()
    {
        var seen = new List<string>();
        var options = ModbusDriverFactoryExtensions.ParseOptions("m1", """{"host":"10.0.0.1","port":502}""");
        var driver = new ModbusDriver(options, "m1", opts =>
        {
            seen.Add($"{opts.Host}:{opts.Port}");
            return new NeverConnectingTransport();
        });

        try { await driver.ReinitializeAsync("{}", CancellationToken.None); }
        catch { /* connect failure is expected */ }

        seen.ShouldAllBe(s => s == "10.0.0.1:502");
    }

    private sealed class NeverConnectingTransport : IModbusTransport
    {
        public bool IsConnected => false;
        public Task ConnectAsync(CancellationToken cancellationToken)
            => throw new IOException("test transport never connects");
        public Task DisconnectAsync(CancellationToken cancellationToken) => Task.CompletedTask;
        public Task<byte[]> SendAsync(byte unitId, byte[] pdu, CancellationToken cancellationToken)
            => throw new IOException("test transport never connects");
        public void Dispose() { }
        public ValueTask DisposeAsync() => ValueTask.CompletedTask;
    }
}
