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

/// <summary>
///     Tracks the Recovering-band dwell for a node after a <c>Faulted → Healthy</c> transition.
///     Per decision #154 and Phase 6.3 Stream B.4 a node that has just returned to health stays
///     in the Recovering band (180 Primary / 30 Backup) until BOTH: (a) the configured
///     <see cref="DwellTime"/> has elapsed, AND (b) at least one successful publish-witness
///     read has been observed.
/// </summary>
/// <remarks>
///     Purely in-memory, no I/O. The coordinator feeds events into <see cref="MarkFaulted"/>,
///     <see cref="MarkRecovered"/>, and <see cref="RecordPublishWitness"/>; <see cref="IsDwellMet"/>
///     becomes true only after both conditions converge.
/// </remarks>
public sealed class RecoveryStateManager
{
    private readonly TimeSpan _dwellTime;
    private readonly TimeProvider _timeProvider;

    /// <summary>Last time the node transitioned Faulted → Healthy. Null until first recovery.</summary>
    private DateTime? _recoveredUtc;

    /// <summary>True once a publish-witness read has succeeded after the last recovery.</summary>
    private bool _witnessed;

    public TimeSpan DwellTime => _dwellTime;

    public RecoveryStateManager(TimeSpan? dwellTime = null, TimeProvider? timeProvider = null)
    {
        _dwellTime = dwellTime ?? TimeSpan.FromSeconds(60);
        _timeProvider = timeProvider ?? TimeProvider.System;
    }

    /// <summary>Report that the node has entered the Faulted state.</summary>
    public void MarkFaulted()
    {
        _recoveredUtc = null;
        _witnessed = false;
    }

    /// <summary>Report that the node has transitioned Faulted → Healthy; dwell clock starts now.</summary>
    public void MarkRecovered()
    {
        _recoveredUtc = _timeProvider.GetUtcNow().UtcDateTime;
        _witnessed = false;
    }

    /// <summary>Report a successful publish-witness read.</summary>
    public void RecordPublishWitness() => _witnessed = true;

    /// <summary>
    ///     True when the dwell is considered met: either the node never faulted in the first
    ///     place, or both (dwell time elapsed + publish witness recorded) since the last
    ///     recovery. False means the coordinator should report Recovering-band ServiceLevel.
    /// </summary>
    public bool IsDwellMet()
    {
        if (_recoveredUtc is null) return true; // never faulted → dwell N/A

        if (!_witnessed) return false;

        var elapsed = _timeProvider.GetUtcNow().UtcDateTime - _recoveredUtc.Value;
        return elapsed >= _dwellTime;
    }
}
