using System;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Stability;

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

[Trait("Category", "Unit")]
public sealed class MemoryWatchdogTests
{
    private const long Mb = 1024 * 1024;

    [Fact]
    public void Baseline_sample_returns_None()
    {
        var w = new MemoryWatchdog(baselineBytes: 300 * Mb);
        w.Sample(320 * Mb, DateTime.UtcNow).ShouldBe(WatchdogAction.None);
    }

    [Fact]
    public void Warn_threshold_uses_larger_of_1_5x_or_plus_200MB()
    {
        // Baseline 300 → warn threshold = max(450, 500) = 500 MB
        var w = new MemoryWatchdog(baselineBytes: 300 * Mb);
        w.Sample(499 * Mb, DateTime.UtcNow).ShouldBe(WatchdogAction.None);
        w.Sample(500 * Mb, DateTime.UtcNow).ShouldBe(WatchdogAction.Warn);
    }

    [Fact]
    public void Soft_recycle_triggers_at_2x_or_plus_200MB_whichever_larger()
    {
        // Baseline 400 → soft = max(800, 600) = 800 MB
        var w = new MemoryWatchdog(baselineBytes: 400 * Mb);
        w.Sample(799 * Mb, DateTime.UtcNow).ShouldBe(WatchdogAction.Warn);
        w.Sample(800 * Mb, DateTime.UtcNow).ShouldBe(WatchdogAction.SoftRecycle);
    }

    [Fact]
    public void Hard_kill_triggers_at_absolute_ceiling()
    {
        var w = new MemoryWatchdog(baselineBytes: 1000 * Mb);
        w.Sample(1501 * Mb, DateTime.UtcNow).ShouldBe(WatchdogAction.HardKill);
    }

    [Fact]
    public void Sustained_slope_triggers_soft_recycle_before_absolute_threshold()
    {
        // Baseline 1000 MB → warn = 1200, soft = 2000 (absolute). Slope 6 MB/min over 30 min = 180 MB
        // delta — still well below the absolute soft threshold; slope detector must fire on its own.
        var w = new MemoryWatchdog(baselineBytes: 1000 * Mb) { SustainedSlopeBytesPerMinute = 5 * Mb };
        var t0 = new DateTime(2026, 4, 17, 12, 0, 0, DateTimeKind.Utc);

        long rss = 1050 * Mb;
        var slopeFired = false;
        for (var i = 0; i <= 35; i++)
        {
            var action = w.Sample(rss, t0.AddMinutes(i));
            if (action == WatchdogAction.SoftRecycle) { slopeFired = true; break; }
            rss += 6 * Mb;
        }

        slopeFired.ShouldBeTrue("slope detector should fire once the 30-min window fills");
    }
}
