using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Health;

/// <summary>
///     Pure-logic merger for the per-host connectivity entries that
///     <see cref="IHostConnectivityProbe"/> surfaces. Holds the current set of host
///     statuses (one synthetic top-level transport entry plus one entry per
///     <c>$WinPlatform</c>/<c>$AppEngine</c> probe) and emits
///     <see cref="OnHostStatusChanged"/> only when an upsert actually changes a host's
///     <see cref="HostState"/> — re-asserting the same state is a no-op so a stable
///     <c>ScanState=Running</c> burst doesn't fan out duplicate transitions.
/// </summary>
/// <remarks>
///     This class owns the de-dup + diff logic that lived in
///     <c>GalaxyProxyDriver.OnHostConnectivityUpdate</c> in v1. The watcher
///     (<see cref="PerPlatformProbeWatcher"/>) and the transport forwarder
///     (<see cref="HostConnectivityForwarder"/>) both feed this aggregator; the
///     <see cref="GalaxyDriver"/> consumes <see cref="Snapshot"/> from
///     <c>IHostConnectivityProbe.GetHostStatuses()</c> and re-raises
///     <see cref="OnHostStatusChanged"/> as the driver-level event (v1's
///     <c>GalaxyProxyDriver.OnHostConnectivityUpdate</c> equivalent).
/// </remarks>
public sealed class HostStatusAggregator
{
    private readonly object _lock = new();
    private readonly Dictionary<string, HostConnectivityStatus> _byHost =
        new(StringComparer.OrdinalIgnoreCase);

    /// <summary>
    ///     Fires when an <see cref="Update"/> call either introduces a new host or
    ///     transitions an existing host's <see cref="HostState"/>. Handlers run
    ///     outside the internal lock so they can safely re-enter the aggregator
    ///     (e.g. the driver re-broadcasting through <c>IHostConnectivityProbe</c>).
    /// </summary>
    public event EventHandler<HostStatusChangedEventArgs>? OnHostStatusChanged;

    /// <summary>
    ///     Snapshot the current host set. Suitable as the body of
    ///     <c>IHostConnectivityProbe.GetHostStatuses()</c>.
    /// </summary>
    /// <returns>The current set of tracked host connectivity statuses.</returns>
    public IReadOnlyList<HostConnectivityStatus> Snapshot()
    {
        lock (_lock)
        {
            return [.. _byHost.Values];
        }
    }

    /// <summary>
    ///     Upsert the supplied status by <see cref="HostConnectivityStatus.HostName"/>.
    ///     Raises <see cref="OnHostStatusChanged"/> when the host is newly tracked
    ///     (previous state reported as <see cref="HostState.Unknown"/>) or when its
    ///     state value differs from the last cached entry. Re-asserting the same
    ///     state is silent.
    /// </summary>
    /// <param name="status">The host connectivity status to upsert.</param>
    public void Update(HostConnectivityStatus status)
    {
        ArgumentNullException.ThrowIfNull(status);

        HostState previous;
        bool changed;
        lock (_lock)
        {
            if (_byHost.TryGetValue(status.HostName, out var existing))
            {
                previous = existing.State;
                changed = existing.State != status.State;
            }
            else
            {
                previous = HostState.Unknown;
                changed = true;
            }

            _byHost[status.HostName] = status;
        }

        if (changed)
        {
            OnHostStatusChanged?.Invoke(this,
                new HostStatusChangedEventArgs(status.HostName, previous, status.State));
        }
    }

    /// <summary>
    ///     Drop a host entirely (e.g. after a redeploy removes a Platform). No event
    ///     is fired — observers only react to live transitions, not topology
    ///     reductions. Returns <c>true</c> when the host was tracked.
    /// </summary>
    /// <param name="hostName">The name of the host to remove.</param>
    /// <returns>True if the host was tracked and removed; false if it was not tracked.</returns>
    public bool Remove(string hostName)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(hostName);
        lock (_lock)
        {
            return _byHost.Remove(hostName);
        }
    }
}
