using System.Net.Sockets;
using NModbus;
using Xunit;

namespace Mbproxy.Tests.Sim;

/// <summary>
/// End-to-end smoke tests that verify the pymodbus DL205 simulator is reachable and
/// serves the expected seeded register values from <c>tests/sim/dl205.json</c>.
/// </summary>
/// <remarks>
/// All three tests call <see cref="Assert.Skip"/> when
/// <see cref="DL205SimulatorFixture.SkipReason"/> is non-null (Python or pymodbus
/// unavailable). This is the expected "green" outcome on machines without Python.
/// </remarks>
[Collection(nameof(DL205SimulatorCollection))]
[Trait("Category", "E2E")]
public sealed class SimulatorSmokeTests
{
    private readonly DL205SimulatorFixture _sim;

    public SimulatorSmokeTests(DL205SimulatorFixture sim)
    {
        _sim = sim;
    }

    /// <summary>
    /// Verifies that the simulator process is running and accepts a plain TCP
    /// connection on its allocated port.
    /// </summary>
    [Fact(Timeout = 5_000)]
    public async Task Simulator_AcceptsTcpConnection()
    {
        if (_sim.SkipReason is not null)
            Assert.Skip(_sim.SkipReason);

        using var client = new TcpClient();
        await client.ConnectAsync(_sim.Host, _sim.Port, TestContext.Current.CancellationToken);

        Assert.True(client.Connected,
            "TcpClient should be connected to the simulator.");
    }

    /// <summary>
    /// Reads holding register 0 via FC03 and expects the DL205 marker value
    /// <c>0xCAFE</c> (51966 decimal). This proves that the dl205.json profile is
    /// actually loaded — a bare pymodbus server with no profile returns 0.
    /// </summary>
    [Fact(Timeout = 5_000)]
    public async Task Simulator_FC03_ReturnsSeededValue_AtHR0_0xCAFE()
    {
        if (_sim.SkipReason is not null)
            Assert.Skip(_sim.SkipReason);

        using var client = new TcpClient();
        await client.ConnectAsync(_sim.Host, _sim.Port, TestContext.Current.CancellationToken);

        var factory = new ModbusFactory();
        var master = factory.CreateMaster(client);

        // FC03: read 1 holding register at address 0, unit ID 1.
        ushort[] registers = master.ReadHoldingRegisters(slaveAddress: 1, startAddress: 0, numberOfPoints: 1);

        Assert.Equal(0xCAFE, registers[0]);
    }

    /// <summary>
    /// Reads holding register 1072 via FC03 and expects raw BCD value
    /// <c>0x1234</c> (4660 decimal). This register represents decimal 1234 stored as
    /// BCD nibbles. The end-to-end rewriter test reads the same register through the
    /// proxy and asserts binary 1234 — proving the proxy rewrote the response.
    /// </summary>
    [Fact(Timeout = 5_000)]
    public async Task Simulator_FC03_ReturnsBCD_RawValueAtHR1072_0x1234()
    {
        if (_sim.SkipReason is not null)
            Assert.Skip(_sim.SkipReason);

        using var client = new TcpClient();
        await client.ConnectAsync(_sim.Host, _sim.Port, TestContext.Current.CancellationToken);

        var factory = new ModbusFactory();
        var master = factory.CreateMaster(client);

        // FC03: read 1 holding register at address 1072, unit ID 1.
        // dl205.json seeds: addr 1072, value 4660 (= 0x1234).
        ushort[] registers = master.ReadHoldingRegisters(slaveAddress: 1, startAddress: 1072, numberOfPoints: 1);

        Assert.Equal(0x1234, registers[0]); // raw BCD nibbles, NOT binary 1234
    }
}
