namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Proxy.Supervisor;

/// <summary>
///     Crash-loop circuit breaker per <c>driver-stability.md</c>:
///     3 crashes within 5 min → open with escalating cooldown 1h → 4h → 24h manual. A sticky
///     alert stays until the operator explicitly resets.
/// </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>
    ///     Called by the supervisor each time the host process exits unexpectedly. Returns
    ///     <c>false</c> when the breaker is open — supervisor must not respawn.
    /// </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; // manual reset required
            }
            if (utcNow - openedAt < cooldown)
            {
                cooldownRemaining = cooldown - (utcNow - openedAt);
                return false;
            }

            // Cooldown elapsed — close the breaker but keep the sticky alert per spec.
            _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;
    }
}
