using Akka.Actor;
using Akka.Cluster;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Health;
using ZB.MOM.WW.ScadaBridge.HealthMonitoring;
using ZB.MOM.WW.ScadaBridge.Host.Actors;

namespace ZB.MOM.WW.ScadaBridge.Host.Health;

/// <summary>
/// Provides cluster node statuses from Akka.NET cluster membership for health reporting.
/// </summary>
public class AkkaClusterNodeProvider : IClusterNodeProvider
{
    private readonly AkkaHostedService _akkaService;
    private readonly string _siteRole;

    /// <summary>
    /// Initializes a new <see cref="AkkaClusterNodeProvider"/>.
    /// </summary>
    /// <param name="akkaService">The Akka hosted service providing access to the actor system.</param>
    /// <param name="siteRole">The Akka cluster role used to filter relevant member nodes.</param>
    public AkkaClusterNodeProvider(AkkaHostedService akkaService, string siteRole)
    {
        _akkaService = akkaService;
        _siteRole = siteRole;
    }

    /// <inheritdoc />
    public bool SelfIsPrimary
    {
        get
        {
            var system = _akkaService.ActorSystem;
            if (system == null) return false;
            var cluster = Cluster.Get(system);
            return ClusterActivityEvaluator.SelfIsOldest(cluster, _siteRole);
        }
    }

    /// <inheritdoc />
    public IReadOnlyList<NodeStatus> GetClusterNodes()
    {
        var system = _akkaService.ActorSystem;
        if (system == null) return [];

        var cluster = Cluster.Get(system);
        var oldest = ClusterActivityEvaluator.OldestUpMember(cluster, _siteRole);

        var nodes = new List<NodeStatus>();
        foreach (var member in cluster.State.Members)
        {
            if (!member.HasRole(_siteRole))
                continue;

            var hostname = member.Address.Host ?? member.Address.ToString();
            var isOnline = member.Status == MemberStatus.Up;
            var isPrimary = oldest != null && member.UniqueAddress.Equals(oldest.UniqueAddress);
            var role = isPrimary ? "Primary" : "Standby";

            nodes.Add(new NodeStatus(hostname, isOnline, role));
        }

        // If we have unreachable members, add them as offline
        foreach (var unreachable in cluster.State.Unreachable)
        {
            if (!unreachable.HasRole(_siteRole))
                continue;

            // Don't duplicate if already in members list
            if (nodes.Any(n => n.Hostname == (unreachable.Address.Host ?? unreachable.Address.ToString())))
                continue;

            var hostname = unreachable.Address.Host ?? unreachable.Address.ToString();
            nodes.Add(new NodeStatus(hostname, false, "Standby"));
        }

        return nodes;
    }
}
