namespace ZB.MOM.WW.OtOpcUa.Server.Redundancy;

/// <summary>
///     Latest observed reachability of the peer node per the Phase 6.3 Stream B.1/B.2 two-layer
///     probe model. HTTP layer is the fast-fail; UA layer is authoritative.
/// </summary>
/// <remarks>
///     Fed into the <see cref="ServiceLevelCalculator"/> as <c>peerHttpHealthy</c> +
///     <c>peerUaHealthy</c>. The concrete probe loops (<c>PeerHttpProbeLoop</c> +
///     <c>PeerUaProbeLoop</c>) live in a Stream B runtime follow-up — this type is the
///     contract the publisher reads; probers write via
///     <see cref="PeerReachabilityTracker"/>.
/// </remarks>
public sealed record PeerReachability(bool HttpHealthy, bool UaHealthy)
{
    public static readonly PeerReachability Unknown = new(false, false);
    public static readonly PeerReachability FullyHealthy = new(true, true);

    /// <summary>True when both probes report healthy — the <c>ServiceLevelCalculator</c>'s peerReachable gate.</summary>
    public bool BothHealthy => HttpHealthy && UaHealthy;
}

/// <summary>
///     Thread-safe holder of the latest <see cref="PeerReachability"/> per peer NodeId. Probe
///     loops call <see cref="Update"/>; the <see cref="RedundancyStatePublisher"/> reads via
///     <see cref="Get"/>.
/// </summary>
public sealed class PeerReachabilityTracker
{
    private readonly System.Collections.Concurrent.ConcurrentDictionary<string, PeerReachability> _byPeer =
        new(StringComparer.OrdinalIgnoreCase);

    public void Update(string peerNodeId, PeerReachability reachability)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(peerNodeId);
        _byPeer[peerNodeId] = reachability ?? throw new ArgumentNullException(nameof(reachability));
    }

    /// <summary>Current reachability for a peer. Returns <see cref="PeerReachability.Unknown"/> when not yet probed.</summary>
    public PeerReachability Get(string peerNodeId) =>
        _byPeer.TryGetValue(peerNodeId, out var r) ? r : PeerReachability.Unknown;
}
