using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Proxy.Supervisor;

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

[Trait("Category", "Unit")]
public sealed class CircuitBreakerTests
{
    [Fact]
    public void First_three_crashes_within_window_allow_respawn()
    {
        var breaker = new CircuitBreaker();
        var t0 = new DateTime(2026, 4, 17, 12, 0, 0, DateTimeKind.Utc);

        breaker.TryRecordCrash(t0, out _).ShouldBeTrue();
        breaker.TryRecordCrash(t0.AddSeconds(30), out _).ShouldBeTrue();
        breaker.TryRecordCrash(t0.AddSeconds(60), out _).ShouldBeTrue();
    }

    [Fact]
    public void Fourth_crash_within_window_opens_breaker_with_sticky_alert()
    {
        var breaker = new CircuitBreaker();
        var t0 = new DateTime(2026, 4, 17, 12, 0, 0, DateTimeKind.Utc);

        for (var i = 0; i < 3; i++) breaker.TryRecordCrash(t0.AddSeconds(i * 30), out _);

        breaker.TryRecordCrash(t0.AddSeconds(120), out var remaining).ShouldBeFalse();
        remaining.ShouldBe(TimeSpan.FromHours(1));
        breaker.StickyAlertActive.ShouldBeTrue();
    }

    [Fact]
    public void Cooldown_escalates_1h_then_4h_then_manual()
    {
        var breaker = new CircuitBreaker();
        var t0 = new DateTime(2026, 4, 17, 12, 0, 0, DateTimeKind.Utc);

        // Open once.
        for (var i = 0; i < 4; i++) breaker.TryRecordCrash(t0.AddSeconds(i * 30), out _);

        // Cooldown starts when the breaker opens (the 4th crash, at t0+90s). Jump past 1h from there.
        var openedAt = t0.AddSeconds(90);
        var afterFirstCooldown = openedAt.AddHours(1).AddMinutes(1);
        breaker.TryRecordCrash(afterFirstCooldown, out _).ShouldBeTrue("cooldown elapsed, breaker closes for a try");

        // Second trip: within 5 min, breaker opens again with 4h cooldown. The crash that trips
        // it is the 3rd retry since the cooldown closed (afterFirstCooldown itself counted as 1).
        breaker.TryRecordCrash(afterFirstCooldown.AddSeconds(30), out _).ShouldBeTrue();
        breaker.TryRecordCrash(afterFirstCooldown.AddSeconds(60), out _).ShouldBeTrue();
        breaker.TryRecordCrash(afterFirstCooldown.AddSeconds(90), out var cd2).ShouldBeFalse(
            "4th crash within window reopens the breaker");
        cd2.ShouldBe(TimeSpan.FromHours(4));

        // Third trip: 4h elapsed, breaker closes for a try, then reopens with MaxValue (manual only).
        var reopenedAt = afterFirstCooldown.AddSeconds(90);
        var afterSecondCooldown = reopenedAt.AddHours(4).AddMinutes(1);
        breaker.TryRecordCrash(afterSecondCooldown, out _).ShouldBeTrue();
        breaker.TryRecordCrash(afterSecondCooldown.AddSeconds(30), out _).ShouldBeTrue();
        breaker.TryRecordCrash(afterSecondCooldown.AddSeconds(60), out _).ShouldBeTrue();
        breaker.TryRecordCrash(afterSecondCooldown.AddSeconds(90), out var cd3).ShouldBeFalse();
        cd3.ShouldBe(TimeSpan.MaxValue);
    }

    [Fact]
    public void ManualReset_clears_sticky_alert_and_crash_history()
    {
        var breaker = new CircuitBreaker();
        var t0 = DateTime.UtcNow;
        for (var i = 0; i < 4; i++) breaker.TryRecordCrash(t0.AddSeconds(i * 30), out _);

        breaker.ManualReset();
        breaker.StickyAlertActive.ShouldBeFalse();

        breaker.TryRecordCrash(t0.AddMinutes(10), out _).ShouldBeTrue();
    }
}
