using Akka.Actor;
using Akka.Cluster;
using Microsoft.Extensions.DependencyInjection;

namespace ZB.MOM.WW.Health.Akka;

/// <summary>
/// <see cref="IActiveNodeGate"/> implementation that computes <see cref="IsActiveNode"/> directly
/// from the Akka cluster state — the local node is the <b>oldest Up member</b>, optionally within a
/// role scope. Register as a singleton.
/// </summary>
/// <remarks>
///     <para>
///         Shares its rule with <see cref="ActiveNodeHealthCheck"/> via
///         <see cref="ClusterActiveNode"/>, so an endpoint gated by this type and the
///         <c>/health/active</c> probe an orchestrator routes by can never disagree about which node
///         is in charge.
///     </para>
///     <para>
///         <b>Behaviour change in 0.3.0:</b> through 0.2.1 this gate selected by
///         <c>ClusterState.Leader</c>, which is address-ordered and diverges from singleton placement
///         after any restart. See <see cref="ClusterActiveNode"/>.
///     </para>
///     <para>
///         The <see cref="ActorSystem"/> is resolved lazily from the service provider; if it is not
///         yet available — e.g. during startup before Akka is initialised —
///         <see cref="IsActiveNode"/> returns <c>false</c> (the safe default during startup, matching
///         the standby case). This gate reads the cluster state directly and does not resolve
///         <see cref="ActiveNodeHealthCheck"/> from DI.
///     </para>
/// </remarks>
public sealed class AkkaActiveNodeGate : IActiveNodeGate
{
    private readonly IServiceProvider _serviceProvider;
    private readonly string? _role;

    /// <summary>Initializes a new <see cref="AkkaActiveNodeGate"/>.</summary>
    /// <param name="serviceProvider">
    /// The application service provider. The <see cref="ActorSystem"/> is resolved lazily; if it is
    /// not yet available <see cref="IsActiveNode"/> returns <c>false</c>.
    /// </param>
    /// <param name="role">
    /// The role to scope the competition to, or <c>null</c> (the default) to consider every Up
    /// member.
    /// </param>
    public AkkaActiveNodeGate(IServiceProvider serviceProvider, string? role = null)
    {
        _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
        _role = role;
    }

    /// <inheritdoc />
    public bool IsActiveNode
    {
        get
        {
            var system = _serviceProvider.GetService<ActorSystem>();
            if (system is null)
                return false;

            try
            {
                return ClusterActiveNode.SelfIsActive(Cluster.Get(system), _role);
            }
            catch (Exception)
            {
                // ActorSystem exists but the cluster is not initialised yet. Same startup-safety
                // rule as the health check, expressed as the gate's safe default: not active.
                return false;
            }
        }
    }
}
