namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Supervisor;

/// <summary>
///     Crash-loop circuit breaker for the FOCAS Host. Matches Galaxy Tier-C defaults:
///     3 crashes within 5 minutes opens the breaker; cooldown escalates 1h → 4h → manual
///     reset. A sticky alert stays live until the operator explicitly clears it so
///     recurring crashes can't silently burn through the cooldown ladder overnight.
/// </summary>
public sealed class CircuitBreaker
{
    public int CrashesAllowedPerWindow { get; init; } = 3;
    public TimeSpan Window { get; init; } = TimeSpan.FromMinutes(5);

    public TimeSpan[] CooldownEscalation { get; init; } =
        [TimeSpan.FromHours(1), TimeSpan.FromHours(4), TimeSpan.MaxValue];

    private readonly List<DateTime> _crashesUtc = [];
    private DateTime? _openSinceUtc;
    private int _escalationLevel;
    public bool StickyAlertActive { get; private set; }

    /// <summary>
    ///     Records a crash + returns <c>true</c> if the supervisor may respawn. On
    ///     <c>false</c>, <paramref name="cooldownRemaining"/> is how long to wait before
    ///     trying again (<c>TimeSpan.MaxValue</c> means manual reset required).
    /// </summary>
    public bool TryRecordCrash(DateTime utcNow, out TimeSpan cooldownRemaining)
    {
        if (_openSinceUtc is { } openedAt)
        {
            var cooldown = CooldownEscalation[Math.Min(_escalationLevel, CooldownEscalation.Length - 1)];
            if (cooldown == TimeSpan.MaxValue)
            {
                cooldownRemaining = TimeSpan.MaxValue;
                return false;
            }
            if (utcNow - openedAt < cooldown)
            {
                cooldownRemaining = cooldown - (utcNow - openedAt);
                return false;
            }

            _openSinceUtc = null;
            _escalationLevel++;
        }

        _crashesUtc.RemoveAll(t => utcNow - t > Window);
        _crashesUtc.Add(utcNow);

        if (_crashesUtc.Count > CrashesAllowedPerWindow)
        {
            _openSinceUtc = utcNow;
            StickyAlertActive = true;
            cooldownRemaining = CooldownEscalation[Math.Min(_escalationLevel, CooldownEscalation.Length - 1)];
            return false;
        }

        cooldownRemaining = TimeSpan.Zero;
        return true;
    }

    public void ManualReset()
    {
        _crashesUtc.Clear();
        _openSinceUtc = null;
        _escalationLevel = 0;
        StickyAlertActive = false;
    }
}
