using Akka.Actor;
using Akka.Cluster.Tools.Client;
using Akka.Configuration;
using Shouldly;
using Xunit;

namespace ZB.MOM.WW.OtOpcUa.Cluster.Tests;

/// <summary>
///     Asserts the ClusterClient / receptionist / frame-size settings that are actually <b>in
///     force</b> on a real <see cref="ActorSystem"/> built from the shipped base config.
/// </summary>
/// <remarks>
///     Asserting the <c>akka.conf</c> text instead would pass while a competing HOCON fragment
///     silently overrode the value — the failure mode recorded at length in
///     <c>ServiceCollectionExtensions.BuildDowningHocon</c> and pinned the same way by
///     <c>SplitBrainResolverActivationTests</c>. In this codebase the effective value is the only
///     one worth asserting.
/// </remarks>
public class MeshTransportHoconTests : IDisposable
{
    private readonly ActorSystem _sys;

    public MeshTransportHoconTests()
    {
        var config = ConfigurationFactory
            .ParseString("akka.remote.dot-netty.tcp.port = 0\nakka.cluster.seed-nodes = []")
            .WithFallback(HoconLoader.LoadBaseConfig());
        _sys = ActorSystem.Create("mesh-hocon-probe", config);
    }

    [Fact]
    public void Cluster_client_buffering_is_disabled()
    {
        // Asserted through ClusterClientSettings — the object the client is actually built from —
        // NOT through Config.GetInt. GetInt returns 0 for a MISSING key, so a config assertion here
        // passes identically whether the setting is explicitly 0 or absent with Akka's default of
        // 1000 in force. It was green before this feature existed; that is the whole defect class.
        //
        // buffer-size = 0 means "drop now if the receptionist is unknown". Buffering would replay a
        // DispatchDeployment on reconnect and apply a deployment the coordinator already sealed as
        // TimedOut, leaving the node running a configuration the database says failed. This is a
        // behaviour decision, not a tuning knob.
        ClusterClientSettings.Create(_sys).BufferSize.ShouldBe(0);
    }

    [Fact]
    public void Cluster_client_retries_forever_rather_than_self_stopping()
    {
        // reconnect-timeout = off surfaces as a null TimeSpan on the settings object. A central node
        // down for an hour must not require a driver-node restart to become reachable again.
        ClusterClientSettings.Create(_sys).ReconnectTimeout.ShouldBeNull();
    }

    [Fact]
    public void Receptionist_serves_every_member_regardless_of_role()
    {
        // An empty role is what makes "registered per node, not as a singleton" work: every member
        // hosts a receptionist, so ClusterClient contact rotation reaches whichever node answers.
        // Scoping this to a role would make the boundary reachable through one node only.
        ClusterReceptionistSettings.Create(_sys).Role.ShouldBeNull();
    }

    [Fact]
    public void Oversized_frames_are_logged_rather_than_dropped_silently()
    {
        // The sister project's most expensive incident. A message over the frame limit is dropped
        // WITHOUT tearing down the association: heartbeats keep flowing, the node still reports
        // healthy, and the only symptom is a command that never arrived. log-frame-size-exceeding
        // converts that silence into a log line naming the offending size.
        _sys.Settings.Config.GetByteSize("akka.remote.dot-netty.tcp.maximum-frame-size").ShouldBe(128000);
        _sys.Settings.Config.GetByteSize("akka.remote.dot-netty.tcp.log-frame-size-exceeding").ShouldBe(32000);
    }

    [Fact]
    public void Receptionist_extension_is_registered_at_startup()
    {
        // Not auto-started. Without this entry the receptionist materialises only if some code path
        // happens to call ClusterClientReceptionist.Get(system) first, which makes "is the boundary
        // listening?" depend on startup ordering rather than on configuration.
        _sys.Settings.Config.GetStringList("akka.extensions")
            .ShouldContain(s => s.Contains("ClusterClientReceptionistExtensionProvider"));
    }

    [Fact]
    public void Distributed_pub_sub_extension_survives_alongside_the_receptionist()
    {
        // The dark switch keeps both transports live for one phase, and DPS additionally carries
        // traffic Phase 2 does not touch at all — node-local alarm-command publishes and the
        // node<->node redundancy-state probe leg. Losing it here would break far more than deploys.
        _sys.Settings.Config.GetStringList("akka.extensions")
            .ShouldContain(s => s.Contains("DistributedPubSubExtensionProvider"));
    }

    public void Dispose()
    {
        _sys.Terminate().Wait(TimeSpan.FromSeconds(5));
        GC.SuppressFinalize(this);
    }
}
