using Akka.Actor;
using Akka.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Commons.Interfaces;

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

/// <summary>
/// Guards <see cref="ClusterRoleInfo.ClusterRole"/> / <see cref="ClusterRoleInfo.ClusterId"/> — the
/// node's OWN cluster-scoped role (per-cluster mesh, Phase 6), derived from the node's configured
/// <see cref="AkkaClusterOptions.Roles"/> at construction time, NOT from live cluster membership.
/// That distinction matters: this identity must be readable before the cluster forms, so later
/// Phase-6 work (singleton registration, a startup validator) can act on it at host-build time.
/// </summary>
public sealed class ClusterRoleInfoTests
{
    private static AkkaClusterOptions SampleOptions(params string[] roles) => new()
    {
        Port = 0,
        Hostname = "127.0.0.1",
        PublicHostname = "127.0.0.1",
        SeedNodes = Array.Empty<string>(),
        Roles = roles,
    };

    /// <summary>
    /// Boots a real host through the production <see cref="ServiceCollectionExtensions.WithOtOpcUaClusterBootstrap"/>
    /// entry point (mirrors <c>SplitBrainResolverActivationTests</c>) — <see cref="ClusterRoleInfo"/>'s
    /// constructor needs a real clustered <see cref="ActorSystem"/> to subscribe to cluster events,
    /// even though the properties under test never touch live membership — resolves
    /// <see cref="IClusterRoleInfo"/> from DI, and returns its <c>(ClusterRole, ClusterId)</c> pair
    /// after tearing the host down. Both start/stop live in this helper rather than the test methods
    /// so the CancellationToken-less calls stay off xunit1051's radar (mirrors
    /// <c>SplitBrainResolverActivationTests.EffectiveConfigAsync</c>).
    /// </summary>
    private static async Task<(string? ClusterRole, string? ClusterId)> ResolveAsync(AkkaClusterOptions options)
    {
        var builder = Host.CreateDefaultBuilder();
        builder.ConfigureServices(services =>
        {
            services.AddSingleton<IOptions<AkkaClusterOptions>>(Options.Create(options));
            services.AddAkka("otopcua-role-info-test", (ab, sp) => ab.WithOtOpcUaClusterBootstrap(sp));
            services.AddSingleton<IClusterRoleInfo, ClusterRoleInfo>();
        });

        using var host = builder.Build();
        await host.StartAsync();
        try
        {
            var roleInfo = host.Services.GetRequiredService<IClusterRoleInfo>();
            return (roleInfo.ClusterRole, roleInfo.ClusterId);
        }
        finally
        {
            await host.StopAsync();
        }
    }

    /// <summary>A node configured with a single cluster-scoped role exposes it and its ClusterId.</summary>
    [Fact]
    public async Task Single_cluster_role_exposes_role_and_id()
    {
        var (clusterRole, clusterId) = await ResolveAsync(SampleOptions("driver", "cluster-SITE-A"));

        clusterRole.ShouldBe("cluster-SITE-A");
        clusterId.ShouldBe("SITE-A");
    }

    /// <summary>A node configured with only fixed roles carries no cluster identity.</summary>
    [Fact]
    public async Task No_cluster_role_configured_yields_null()
    {
        var (clusterRole, clusterId) = await ResolveAsync(SampleOptions("admin", "driver"));

        clusterRole.ShouldBeNull();
        clusterId.ShouldBeNull();
    }

    /// <summary>
    /// More than one cluster-scoped role is a misconfiguration; the first configured wins rather
    /// than throwing.
    /// </summary>
    [Fact]
    public async Task Multiple_cluster_roles_first_wins()
    {
        var (clusterRole, clusterId) = await ResolveAsync(SampleOptions("driver", "cluster-MAIN", "cluster-SITE-B"));

        clusterRole.ShouldBe("cluster-MAIN");
        clusterId.ShouldBe("MAIN");
    }

    /// <summary>An empty role list carries no cluster identity either.</summary>
    [Fact]
    public async Task Empty_roles_yields_null()
    {
        var (clusterRole, clusterId) = await ResolveAsync(SampleOptions());

        clusterRole.ShouldBeNull();
        clusterId.ShouldBeNull();
    }
}
