using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Core.Stability;

namespace ZB.MOM.WW.OtOpcUa.Core.Tests.Stability;

[Trait("Category", "Unit")]
public sealed class MemoryTrackingTests
{
    private static readonly DateTime T0 = new(2026, 4, 19, 12, 0, 0, DateTimeKind.Utc);

    /// <summary>Verifies that warming phase returns Warming until the time window elapses.</summary>
    [Fact]
    public void WarmingUp_Returns_Warming_UntilWindowElapses()
    {
        var tracker = new MemoryTracking(DriverTier.A, TimeSpan.FromMinutes(5));

        tracker.Sample(100_000_000, T0).ShouldBe(MemoryTrackingAction.Warming);
        tracker.Sample(105_000_000, T0.AddMinutes(1)).ShouldBe(MemoryTrackingAction.Warming);
        tracker.Sample(102_000_000, T0.AddMinutes(4.9)).ShouldBe(MemoryTrackingAction.Warming);

        tracker.Phase.ShouldBe(TrackingPhase.WarmingUp);
        tracker.BaselineBytes.ShouldBe(0);
    }

    /// <summary>Verifies that when the window elapses, baseline is captured as median and phase transitions to steady.</summary>
    [Fact]
    public void WindowElapsed_CapturesBaselineAsMedian_AndTransitionsToSteady()
    {
        var tracker = new MemoryTracking(DriverTier.A, TimeSpan.FromMinutes(5));

        tracker.Sample(100_000_000, T0);
        tracker.Sample(200_000_000, T0.AddMinutes(1));
        tracker.Sample(150_000_000, T0.AddMinutes(2));
        var first = tracker.Sample(150_000_000, T0.AddMinutes(5));

        tracker.Phase.ShouldBe(TrackingPhase.Steady);
        tracker.BaselineBytes.ShouldBe(150_000_000L, "median of 4 samples [100, 200, 150, 150] = (150+150)/2 = 150");
        first.ShouldBe(MemoryTrackingAction.None, "150 MB is the baseline itself, well under soft threshold");
    }

    /// <summary>Verifies that tier constants match Decision 146 specification.</summary>
    /// <param name="tier">The driver tier to test.</param>
    /// <param name="expectedMultiplier">Expected growth multiplier.</param>
    /// <param name="expectedFloorMB">Expected floor in megabytes.</param>
    [Theory]
    [InlineData(DriverTier.A, 3, 50)]
    [InlineData(DriverTier.B, 3, 100)]
    [InlineData(DriverTier.C, 2, 500)]
    public void GetTierConstants_MatchesDecision146(DriverTier tier, int expectedMultiplier, long expectedFloorMB)
    {
        var (multiplier, floor) = MemoryTracking.GetTierConstants(tier);
        multiplier.ShouldBe(expectedMultiplier);
        floor.ShouldBe(expectedFloorMB * 1024 * 1024);
    }

    /// <summary>Verifies that soft threshold uses the maximum of multiplier and floor for small baselines.</summary>
    [Fact]
    public void SoftThreshold_UsesMax_OfMultiplierAndFloor_SmallBaseline()
    {
        // Tier A: mult=3, floor=50 MB. Baseline 10 MB → 3×10=30 MB < 10+50=60 MB → floor wins.
        var tracker = WarmupWithBaseline(DriverTier.A, 10L * 1024 * 1024);
        tracker.SoftThresholdBytes.ShouldBe(60L * 1024 * 1024);
    }

    /// <summary>Verifies that soft threshold uses the maximum of multiplier and floor for large baselines.</summary>
    [Fact]
    public void SoftThreshold_UsesMax_OfMultiplierAndFloor_LargeBaseline()
    {
        // Tier A: mult=3, floor=50 MB. Baseline 200 MB → 3×200=600 MB > 200+50=250 MB → multiplier wins.
        var tracker = WarmupWithBaseline(DriverTier.A, 200L * 1024 * 1024);
        tracker.SoftThresholdBytes.ShouldBe(600L * 1024 * 1024);
    }

    /// <summary>Verifies that hard threshold is twice the soft threshold.</summary>
    [Fact]
    public void HardThreshold_IsTwiceSoft()
    {
        var tracker = WarmupWithBaseline(DriverTier.B, 200L * 1024 * 1024);
        tracker.HardThresholdBytes.ShouldBe(tracker.SoftThresholdBytes * 2);
    }

    /// <summary>Verifies that samples below soft threshold return None.</summary>
    [Fact]
    public void Sample_Below_Soft_Returns_None()
    {
        var tracker = WarmupWithBaseline(DriverTier.A, 100L * 1024 * 1024);

        tracker.Sample(200L * 1024 * 1024, T0.AddMinutes(10)).ShouldBe(MemoryTrackingAction.None);
    }

    /// <summary>Verifies that samples at soft threshold return SoftBreach.</summary>
    [Fact]
    public void Sample_AtSoft_Returns_SoftBreach()
    {
        // Tier A, baseline 200 MB → soft = 600 MB. Sample exactly at soft.
        var tracker = WarmupWithBaseline(DriverTier.A, 200L * 1024 * 1024);

        tracker.Sample(tracker.SoftThresholdBytes, T0.AddMinutes(10))
            .ShouldBe(MemoryTrackingAction.SoftBreach);
    }

    /// <summary>Verifies that samples at hard threshold return HardBreach.</summary>
    [Fact]
    public void Sample_AtHard_Returns_HardBreach()
    {
        var tracker = WarmupWithBaseline(DriverTier.A, 200L * 1024 * 1024);

        tracker.Sample(tracker.HardThresholdBytes, T0.AddMinutes(10))
            .ShouldBe(MemoryTrackingAction.HardBreach);
    }

    /// <summary>Verifies that samples above hard threshold return HardBreach.</summary>
    [Fact]
    public void Sample_AboveHard_Returns_HardBreach()
    {
        var tracker = WarmupWithBaseline(DriverTier.A, 200L * 1024 * 1024);

        tracker.Sample(tracker.HardThresholdBytes + 100_000_000, T0.AddMinutes(10))
            .ShouldBe(MemoryTrackingAction.HardBreach);
    }

    private static MemoryTracking WarmupWithBaseline(DriverTier tier, long baseline)
    {
        var tracker = new MemoryTracking(tier, TimeSpan.FromMinutes(5));
        tracker.Sample(baseline, T0);
        tracker.Sample(baseline, T0.AddMinutes(5));
        tracker.BaselineBytes.ShouldBe(baseline);
        return tracker;
    }
}
