using System.Collections.Immutable;
using Akka.Actor;
using Akka.Cluster.Tools.Client;
using Akka.Configuration;
using Akka.TestKit.Xunit2;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.ControlPlane.Communication;

namespace ZB.MOM.WW.OtOpcUa.ControlPlane.Tests.Communication;

/// <summary>
///     Guards the one non-obvious part of client creation: recreating a client with the same name
///     inside a single message handler throws, because <c>Context.Stop</c> is asynchronous and the
///     name stays reserved until termination completes.
/// </summary>
public class MeshClusterClientFactoryTests : TestKit
{
    private static readonly Config TestConfig = ConfigurationFactory.ParseString(@"
        akka.actor.provider = cluster
        akka.remote.dot-netty.tcp.port = 0
        akka.remote.dot-netty.tcp.hostname = 127.0.0.1")
        .WithFallback(ClusterClientReceptionist.DefaultConfig());

    public MeshClusterClientFactoryTests()
        : base(TestConfig)
    {
    }

    // A real ClusterClient pointed at an unreachable contact is safe in TestKit — it simply retries
    // the initial-contact rotation and never fails the test.
    private static ImmutableHashSet<ActorPath> UnreachableContact => ImmutableHashSet.Create(
        ActorPath.Parse("akka.tcp://otopcua@127.0.0.1:65501/system/receptionist"));

    [Fact]
    public void Recreating_a_client_in_one_pass_does_not_collide_on_the_actor_name()
    {
        var factory = new DefaultMeshClusterClientFactory();

        var first = factory.Create(Sys, "C1", UnreachableContact);
        Sys.Stop(first);

        // Stop is asynchronous: `first`'s name is still reserved right here. Without the generation
        // suffix this throws InvalidActorNameException — which is precisely what a contact-set
        // change would do in production, where stop-then-recreate happens in one handler.
        var second = factory.Create(Sys, "C1", UnreachableContact);

        second.Path.Name.ShouldNotBe(first.Path.Name);
    }

    [Fact]
    public void Every_incarnation_gets_a_distinct_name()
    {
        var factory = new DefaultMeshClusterClientFactory();

        var names = Enumerable.Range(0, 5)
            .Select(_ => factory.Create(Sys, "C1", UnreachableContact).Path.Name)
            .ToList();

        names.Distinct().Count().ShouldBe(5);
    }

    [Fact]
    public void The_cluster_id_is_woven_into_the_client_actor_name()
    {
        // Per-cluster clients must be distinguishable in logs and the actor tree, and — more
        // importantly — two clusters' clients created on one system must never collide on a name.
        var factory = new DefaultMeshClusterClientFactory();

        var a = factory.Create(Sys, "site-a", UnreachableContact);
        var b = factory.Create(Sys, "site-b", UnreachableContact);

        a.Path.Name.ShouldContain("site-a");
        b.Path.Name.ShouldContain("site-b");
        a.Path.Name.ShouldNotBe(b.Path.Name);
    }

    // Contact-set propagation is deliberately NOT asserted here: reading initial contacts back
    // off a created ClusterClient is not possible, and asserting ClusterClientSettings directly
    // would test Akka rather than this factory. It is covered where it can actually fail — the
    // real two-ActorSystem boundary test, which only delivers if the contacts reached the client.
}
