using System.Net.Sockets;

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

/// <summary>
///     Reachability probe for the RTU-over-TCP Modbus simulator (pymodbus in Docker with
///     <c>framer=rtu</c>, see <c>Docker/docker-compose.yml</c> profile <c>rtu_over_tcp</c>) or a
///     real serial→Ethernet Modbus gateway. Mirrors <see cref="ModbusSimulatorFixture"/> but reads
///     <c>MODBUS_RTU_SIM_ENDPOINT</c> (default <c>10.100.0.35:5021</c> — the shared Docker host, a
///     distinct port from the standard profile's <c>:5020</c> so the two fixtures co-run). TCP-connects
///     once at fixture construction; each test checks <see cref="SkipReason"/> and calls
///     <c>Assert.Skip</c> when the endpoint was unreachable, so a dev box without a running simulator
///     still passes `dotnet test` cleanly.
/// </summary>
/// <remarks>
///     Same one-shot-probe discipline as <see cref="ModbusSimulatorFixture"/>: the probe socket is
///     not held for the life of the fixture (tests open their own <see cref="ModbusRtuOverTcpTransport"/>
///     against the same endpoint), and the fixture is a collection fixture so the probe runs once per
///     session rather than per test.
/// </remarks>
public sealed class ModbusRtuOverTcpFixture : IAsyncDisposable
{
    // :5021 (not the standard profile's :5020) so the rtu_over_tcp container can co-run with the
    // standard container on the shared Docker host. 10.100.0.35 = the shared Docker host (see
    // CLAUDE.md "Docker Workflow"). Override with MODBUS_RTU_SIM_ENDPOINT to point at a real
    // serial→Ethernet Modbus gateway, or a locally-running container.
    private const string DefaultEndpoint = "10.100.0.35:5021";
    private const string EndpointEnvVar = "MODBUS_RTU_SIM_ENDPOINT";

    /// <summary>Gets the host address of the RTU-over-TCP Modbus simulator.</summary>
    public string Host { get; }

    /// <summary>Gets the port of the RTU-over-TCP Modbus simulator.</summary>
    public int Port { get; }

    /// <summary>Gets the skip reason if the simulator is unreachable; otherwise null.</summary>
    public string? SkipReason { get; }

    /// <summary>Initializes a new instance of the <see cref="ModbusRtuOverTcpFixture"/> class.</summary>
    public ModbusRtuOverTcpFixture()
    {
        var raw = Environment.GetEnvironmentVariable(EndpointEnvVar) ?? DefaultEndpoint;
        var parts = raw.Split(':', 2);
        Host = parts[0];
        Port = parts.Length == 2 && int.TryParse(parts[1], out var p) ? p : 502;

        try
        {
            // Force IPv4 on the probe — pymodbus binds 0.0.0.0 (IPv4 only) while .NET default-resolves
            // "localhost" → IPv6 ::1 first and only then tries IPv4, surfacing as a 2s timeout under
            // .NET 10. Resolving + dialing explicit IPv4 sidesteps the dual-stack ordering. (Same
            // rationale as ModbusSimulatorFixture.)
            using var client = new TcpClient(System.Net.Sockets.AddressFamily.InterNetwork);
            var task = client.ConnectAsync(
                System.Net.Dns.GetHostAddresses(Host)
                    .FirstOrDefault(a => a.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
                    ?? System.Net.IPAddress.Loopback,
                Port);
            if (!task.Wait(TimeSpan.FromSeconds(2)) || !client.Connected)
            {
                SkipReason = $"RTU-over-TCP Modbus simulator at {Host}:{Port} did not accept a TCP connection within 2s. " +
                             $"Start the pymodbus Docker container (docker compose -f Docker/docker-compose.yml --profile rtu_over_tcp up -d --build) " +
                             $"or override {EndpointEnvVar}, then re-run.";
            }
        }
        catch (Exception ex)
        {
            SkipReason = $"RTU-over-TCP Modbus simulator at {Host}:{Port} unreachable: {ex.GetType().Name}: {ex.Message}. " +
                         $"Start the pymodbus Docker container (docker compose -f Docker/docker-compose.yml --profile rtu_over_tcp up -d --build) " +
                         $"or override {EndpointEnvVar}, then re-run.";
        }
    }

    /// <inheritdoc />
    public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}

[Xunit.CollectionDefinition(Name)]
public sealed class ModbusRtuOverTcpCollection : Xunit.ICollectionFixture<ModbusRtuOverTcpFixture>
{
    public const string Name = "ModbusRtuOverTcp";
}
