using System.Collections.Concurrent;
using ZB.MOM.WW.ScadaBridge.AuditLog.Payload;

namespace ZB.MOM.WW.ScadaBridge.AuditLog.Central;

/// <summary>
/// Central singleton implementation of
/// <see cref="IAuditCentralHealthSnapshot"/>. Owns thread-safe
/// <see cref="System.Threading.Interlocked"/> counters for
/// <c>CentralAuditWriteFailures</c> + <c>AuditRedactionFailure</c> and a
/// per-site latched stalled-state map fed by the
/// <see cref="SiteAuditTelemetryStalledTracker"/>. Also implements the
/// writer surfaces (<see cref="ICentralAuditWriteFailureCounter"/> +
/// <see cref="IAuditRedactionFailureCounter"/>) so a single concrete object
/// is the source of truth — DI binds those two interfaces to this same
/// singleton instance on the central composition root.
/// </summary>
/// <remarks>
/// <para>
/// <b>Why one type for read + write.</b> The writer interfaces are tiny
/// (<c>Increment()</c>) and the read surface needs visibility of those
/// counters anyway — having a single class own both means the
/// <c>Interlocked</c> field IS the snapshot value, no extra plumbing needed.
/// Mirrors the
/// <see cref="ZB.MOM.WW.ScadaBridge.HealthMonitoring.SiteHealthCollector"/> pattern where
/// the collector both receives and exposes the metric.
/// </para>
/// <para>
/// <b>Stalled-state plumbing.</b> The per-site stalled latch lives directly
/// on this snapshot. <see cref="SiteAuditTelemetryStalledTracker"/> is the
/// EventStream subscriber that pushes
/// <see cref="SiteAuditTelemetryStalledChanged"/> publications in via
/// <see cref="ApplyStalled"/>. Keeping the dictionary on this type (rather
/// than reading the tracker on every access) lets the snapshot be constructed
/// without an <see cref="Akka.Actor.ActorSystem"/> dependency — the tracker
/// is wired up later from the Akka bootstrap, once the system is built.
/// </para>
/// </remarks>
public sealed class AuditCentralHealthSnapshot
    : IAuditCentralHealthSnapshot,
      ICentralAuditWriteFailureCounter,
      IAuditRedactionFailureCounter,
      IAuditInboundCeilingHitsCounter,
      IAuditPurgeFailureCounter
{
    private int _centralAuditWriteFailures;
    private int _auditRedactionFailure;
    private int _auditInboundCeilingHits;
    private int _auditPurgeFailures;
    private readonly ConcurrentDictionary<string, bool> _stalled = new();

    /// <inheritdoc/>
    public int CentralAuditWriteFailures =>
        Interlocked.CompareExchange(ref _centralAuditWriteFailures, 0, 0);

    /// <inheritdoc/>
    public int AuditRedactionFailure =>
        Interlocked.CompareExchange(ref _auditRedactionFailure, 0, 0);

    /// <inheritdoc/>
    public int AuditInboundCeilingHits =>
        Interlocked.CompareExchange(ref _auditInboundCeilingHits, 0, 0);

    /// <inheritdoc/>
    public int PurgeFailures =>
        Interlocked.CompareExchange(ref _auditPurgeFailures, 0, 0);

    /// <inheritdoc/>
    public IReadOnlyDictionary<string, bool> SiteAuditTelemetryStalled =>
        new Dictionary<string, bool>(_stalled);

    /// <summary>
    /// Apply a <see cref="SiteAuditTelemetryStalledChanged"/> publication
    /// observed by <see cref="SiteAuditTelemetryStalledTracker"/>. Public
    /// so the tracker (which lives in the same assembly but is constructed
    /// later from the Akka host) can push without a friend reference;
    /// readers should call <see cref="SiteAuditTelemetryStalled"/>.
    /// </summary>
    /// <param name="evt">The event carrying the site ID and new stalled state.</param>
    public void ApplyStalled(SiteAuditTelemetryStalledChanged evt)
    {
        if (evt is null) return;
        _stalled[evt.SiteId] = evt.Stalled;
    }

    /// <inheritdoc/>
    void ICentralAuditWriteFailureCounter.Increment() =>
        Interlocked.Increment(ref _centralAuditWriteFailures);

    /// <inheritdoc/>
    void IAuditRedactionFailureCounter.Increment() =>
        Interlocked.Increment(ref _auditRedactionFailure);

    /// <inheritdoc/>
    void IAuditInboundCeilingHitsCounter.Increment() =>
        Interlocked.Increment(ref _auditInboundCeilingHits);

    /// <inheritdoc/>
    void IAuditPurgeFailureCounter.Increment() =>
        Interlocked.Increment(ref _auditPurgeFailures);
}
