namespace ZB.MOM.WW.LocalDb.Replication;

/// <summary>
/// A live, read-only view of the replication engine's current state, registered as a singleton for
/// dashboards / health surfaces. Always available (empty/false on a passive or not-yet-connected
/// node); the adapters and the sync session update it as connections and batches come and go.
/// </summary>
public interface ISyncStatus
{
    /// <summary>True while a sync session is actively running (in <c>RunAsync</c>).</summary>
    bool Connected { get; }

    /// <summary>The peer node's identity once the handshake has completed, else null.</summary>
    string? PeerNodeId { get; }

    /// <summary>When the last inbound batch was applied / acked, else null if no sync has occurred.</summary>
    DateTimeOffset? LastSyncUtc { get; }

    /// <summary>The current unacked oplog backlog (entries above the peer's last-acked watermark), or null when unknown (no provider wired / the poll failed) — so a DB failure never masquerades as "0 backlog".</summary>
    long? OplogBacklog { get; }

    /// <summary>Number of connection attempts the initiator has made this lifetime.</summary>
    long ConnectionAttempts { get; }
}

/// <summary>
/// The mutable <see cref="ISyncStatus"/> backing store: thread-safe setters written from the sync
/// session and the adapters, read from arbitrary dashboard / scrape threads. <see cref="OplogBacklog"/>
/// polls <see cref="OplogBacklogProvider"/> on read (the same cheap SELECT the depth gauge uses).
/// </summary>
internal sealed class SyncStatus : ISyncStatus
{
    // A node can run BOTH roles at once (initiator + passive server), so Connected tracks a count
    // of active sessions rather than a boolean — one session ending must not clobber the other.
    private int _activeSessions;
    private string? _peerNodeId;
    private long _lastSyncUtcTicks = -1;
    private long _connectionAttempts;

    /// <summary>Supplies the live unacked backlog for <see cref="OplogBacklog"/>; null / a throwing poll yields a null (unknown) backlog.</summary>
    public Func<long>? OplogBacklogProvider { get; set; }

    public bool Connected => Volatile.Read(ref _activeSessions) > 0;

    public string? PeerNodeId => Volatile.Read(ref _peerNodeId);

    public DateTimeOffset? LastSyncUtc
    {
        get
        {
            var ticks = Interlocked.Read(ref _lastSyncUtcTicks);
            return ticks < 0 ? null : new DateTimeOffset(ticks, TimeSpan.Zero);
        }
    }

    public long? OplogBacklog
    {
        get
        {
            var provider = OplogBacklogProvider;
            if (provider is null)
                return null;
            try
            {
                return provider();
            }
            catch
            {
                return null;
            }
        }
    }

    public long ConnectionAttempts => Interlocked.Read(ref _connectionAttempts);

    public void SessionStarted() => Interlocked.Increment(ref _activeSessions);

    public void SessionEnded() => Interlocked.Decrement(ref _activeSessions);

    public void SetPeerNodeId(string peerNodeId) => Volatile.Write(ref _peerNodeId, peerNodeId);

    public void SetLastSyncUtc(DateTimeOffset value) =>
        Interlocked.Exchange(ref _lastSyncUtcTicks, value.UtcTicks);

    public void SetConnectionAttempts(long value) => Interlocked.Exchange(ref _connectionAttempts, value);
}
