using System.Net.Sockets;
using Mbproxy.Options;

namespace Mbproxy.Proxy;

/// <summary>
/// Applies OS-level TCP keepalive (<c>SO_KEEPALIVE</c> plus the idle-time / probe-interval /
/// probe-count tunables) to a socket. Used on both the backend socket (proxy → PLC) and
/// accepted upstream sockets (client → proxy) so the OS detects a dead peer on an
/// otherwise-idle connection — the DL205/DL260 ECOM never emits keepalives of its own.
/// </summary>
internal static class SocketKeepalive
{
    /// <summary>
    /// Enables TCP keepalive on <paramref name="socket"/> from <paramref name="options"/>.
    /// A no-op when <see cref="KeepaliveOptions.Enabled"/> is <c>false</c>.
    ///
    /// <para>Failures are swallowed: keepalive is a best-effort belt-and-suspenders measure
    /// (the backend application heartbeat is the load-bearing mechanism) and must never
    /// abort a connection. The three TCP tunables are also not honoured on every platform;
    /// a refusal there is benign.</para>
    /// </summary>
    public static void Apply(Socket socket, KeepaliveOptions options)
    {
        if (!options.Enabled) return;

        try
        {
            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);

            // SocketOptionName.TcpKeepAliveTime / TcpKeepAliveInterval are specified in
            // SECONDS; round the configured milliseconds up to at least one second.
            int idleSec     = Math.Max(1, (options.TcpIdleTimeMs      + 999) / 1000);
            int intervalSec = Math.Max(1, (options.TcpProbeIntervalMs + 999) / 1000);

            socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime,       idleSec);
            socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval,   intervalSec);
            socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, options.TcpProbeCount);
        }
        catch (SocketException)
        {
            // Platform refused a tunable — keepalive stays best-effort.
        }
        catch (ObjectDisposedException)
        {
            // Socket closed concurrently — nothing to do.
        }
    }
}
