using NATS.Server.JetStream.Consumers;

namespace NATS.Server.JetStream.Tests.JetStream.Consumers;

/// <summary>
/// Tests for SampleTracker: sample frequency parsing and stochastic latency sampling.
/// Go reference: consumer.go sampleFrequency, shouldSample, parseSampleFrequency.
/// </summary>
public class SampleModeTests
{
    // --- ParseSampleFrequency ---

    [Fact]
    public void ParseSampleFrequency_one_percent()
    {
        var rate = SampleTracker.ParseSampleFrequency("1%");
        rate.ShouldBe(0.01, 1e-9);
    }

    [Fact]
    public void ParseSampleFrequency_fifty_percent()
    {
        var rate = SampleTracker.ParseSampleFrequency("50%");
        rate.ShouldBe(0.5, 1e-9);
    }

    [Fact]
    public void ParseSampleFrequency_hundred_percent()
    {
        var rate = SampleTracker.ParseSampleFrequency("100%");
        rate.ShouldBe(1.0, 1e-9);
    }

    [Fact]
    public void ParseSampleFrequency_zero()
    {
        var rate = SampleTracker.ParseSampleFrequency("0%");
        rate.ShouldBe(0.0, 1e-9);
    }

    [Fact]
    public void ParseSampleFrequency_no_percent_sign()
    {
        var rate = SampleTracker.ParseSampleFrequency("25");
        rate.ShouldBe(0.25, 1e-9);
    }

    [Fact]
    public void ParseSampleFrequency_empty_string()
    {
        var rate = SampleTracker.ParseSampleFrequency("");
        rate.ShouldBe(0.0, 1e-9);
    }

    [Fact]
    public void ParseSampleFrequency_null()
    {
        var rate = SampleTracker.ParseSampleFrequency(null);
        rate.ShouldBe(0.0, 1e-9);
    }

    [Fact]
    public void ParseSampleFrequency_invalid()
    {
        var rate = SampleTracker.ParseSampleFrequency("abc");
        rate.ShouldBe(0.0, 1e-9);
    }

    [Fact]
    public void ParseSampleFrequency_over_100_clamped()
    {
        var rate = SampleTracker.ParseSampleFrequency("200%");
        rate.ShouldBe(1.0, 1e-9);
    }

    // --- ShouldSample ---

    [Fact]
    public void ShouldSample_rate_100_always_samples()
    {
        var tracker = new SampleTracker(1.0);
        for (var i = 0; i < 20; i++)
        {
            tracker.ShouldSample().ShouldBeTrue();
        }
    }

    [Fact]
    public void ShouldSample_rate_0_never_samples()
    {
        var tracker = new SampleTracker(0.0);
        for (var i = 0; i < 20; i++)
        {
            tracker.ShouldSample().ShouldBeFalse();
        }
    }

    [Fact]
    public void ShouldSample_increments_total_deliveries()
    {
        var tracker = new SampleTracker(0.5);
        tracker.TotalDeliveries.ShouldBe(0L);

        tracker.ShouldSample();
        tracker.TotalDeliveries.ShouldBe(1L);

        tracker.ShouldSample();
        tracker.TotalDeliveries.ShouldBe(2L);

        tracker.ShouldSample();
        tracker.TotalDeliveries.ShouldBe(3L);
    }

    [Fact]
    public void ShouldSample_stochastic_with_seeded_random()
    {
        // Use a seeded Random for deterministic results.
        // With seed 42 and rate 0.5, we can predict exact outcomes.
        var rng = new Random(42);
        var tracker = new SampleTracker(0.5, rng);

        // Pre-compute expected outcomes using the same seed.
        var expectedRng = new Random(42);
        var expected = new bool[10];
        for (var i = 0; i < 10; i++)
        {
            expected[i] = expectedRng.NextDouble() < 0.5;
        }

        var actual = new bool[10];
        for (var i = 0; i < 10; i++)
        {
            actual[i] = tracker.ShouldSample();
        }

        actual.ShouldBe(expected);
        tracker.TotalDeliveries.ShouldBe(10L);
    }

    [Fact]
    public void RecordLatency_captures_all_fields()
    {
        var tracker = new SampleTracker(1.0);
        var latency = TimeSpan.FromMilliseconds(42);
        const ulong seq = 7UL;
        const string subject = "orders.new";

        var before = DateTime.UtcNow;
        var sample = tracker.RecordLatency(latency, seq, subject);
        var after = DateTime.UtcNow;

        sample.Sequence.ShouldBe(seq);
        sample.Subject.ShouldBe(subject);
        sample.DeliveryLatency.ShouldBe(latency);
        sample.SampledAtUtc.ShouldBeGreaterThanOrEqualTo(before);
        sample.SampledAtUtc.ShouldBeLessThanOrEqualTo(after);
    }

    [Fact]
    public void SampleCount_tracks_sampled_only()
    {
        // Rate 1.0: every delivery is sampled.
        var allSampled = new SampleTracker(1.0);
        for (var i = 0; i < 5; i++) allSampled.ShouldSample();
        allSampled.SampleCount.ShouldBe(5L);
        allSampled.TotalDeliveries.ShouldBe(5L);

        // Rate 0.0: no delivery is sampled.
        var noneSampled = new SampleTracker(0.0);
        for (var i = 0; i < 5; i++) noneSampled.ShouldSample();
        noneSampled.SampleCount.ShouldBe(0L);
        noneSampled.TotalDeliveries.ShouldBe(5L);
    }
}
