// Tests for service export latency tracker with p50/p90/p99 percentile histogram.
// Go reference: accounts_test.go TestServiceLatency, serviceExportLatencyStats.

using NATS.Server.Auth;

namespace NATS.Server.Auth.Tests.Auth;

public class ServiceLatencyTrackerTests
{
    [Fact]
    public void RecordLatency_IncrementsTotalRequests()
    {
        var tracker = new ServiceLatencyTracker();

        tracker.RecordLatency(10.0);
        tracker.RecordLatency(20.0);
        tracker.RecordLatency(30.0);

        tracker.TotalRequests.ShouldBe(3L);
    }

    [Fact]
    public void GetP50_ReturnsMedian()
    {
        var tracker = new ServiceLatencyTracker();

        foreach (var v in new double[] { 1, 2, 3, 4, 5 })
            tracker.RecordLatency(v);

        // Sorted: [1, 2, 3, 4, 5], index = (int)(0.50 * 4) = 2 → value 3
        tracker.GetP50().ShouldBe(3.0);
    }

    [Fact]
    public void GetP90_ReturnsHighPercentile()
    {
        var tracker = new ServiceLatencyTracker();

        for (var i = 1; i <= 100; i++)
            tracker.RecordLatency(i);

        // Sorted [1..100], index = (int)(0.90 * 99) = (int)89.1 = 89 → value 90
        tracker.GetP90().ShouldBe(90.0);
    }

    [Fact]
    public void GetP99_ReturnsTopPercentile()
    {
        var tracker = new ServiceLatencyTracker();

        for (var i = 1; i <= 100; i++)
            tracker.RecordLatency(i);

        // Sorted [1..100], index = (int)(0.99 * 99) = (int)98.01 = 98 → value 99
        tracker.GetP99().ShouldBe(99.0);
    }

    [Fact]
    public void AverageLatencyMs_CalculatesCorrectly()
    {
        var tracker = new ServiceLatencyTracker();

        tracker.RecordLatency(10.0);
        tracker.RecordLatency(20.0);
        tracker.RecordLatency(30.0);

        tracker.AverageLatencyMs.ShouldBe(20.0);
    }

    [Fact]
    public void MinLatencyMs_ReturnsMinimum()
    {
        var tracker = new ServiceLatencyTracker();

        tracker.RecordLatency(15.0);
        tracker.RecordLatency(5.0);
        tracker.RecordLatency(10.0);

        tracker.MinLatencyMs.ShouldBe(5.0);
    }

    [Fact]
    public void MaxLatencyMs_ReturnsMaximum()
    {
        var tracker = new ServiceLatencyTracker();

        tracker.RecordLatency(5.0);
        tracker.RecordLatency(15.0);
        tracker.RecordLatency(10.0);

        tracker.MaxLatencyMs.ShouldBe(15.0);
    }

    [Fact]
    public void Reset_ClearsSamples()
    {
        var tracker = new ServiceLatencyTracker();

        tracker.RecordLatency(10.0);
        tracker.RecordLatency(20.0);
        tracker.SampleCount.ShouldBe(2);
        tracker.TotalRequests.ShouldBe(2L);

        tracker.Reset();

        tracker.SampleCount.ShouldBe(0);
        tracker.TotalRequests.ShouldBe(0L);
        tracker.AverageLatencyMs.ShouldBe(0.0);
        tracker.MinLatencyMs.ShouldBe(0.0);
        tracker.MaxLatencyMs.ShouldBe(0.0);
        tracker.GetP50().ShouldBe(0.0);
    }

    [Fact]
    public void GetSnapshot_ReturnsImmutableSnapshot()
    {
        var tracker = new ServiceLatencyTracker();

        tracker.RecordLatency(10.0);
        tracker.RecordLatency(20.0);
        tracker.RecordLatency(30.0);

        var snapshot = tracker.GetSnapshot();

        snapshot.TotalRequests.ShouldBe(3L);
        snapshot.SampleCount.ShouldBe(3);
        snapshot.AverageMs.ShouldBe(20.0);
        snapshot.MinMs.ShouldBe(10.0);
        snapshot.MaxMs.ShouldBe(30.0);
        // P50 of [10, 20, 30]: index = (int)(0.50 * 2) = 1 → 20
        snapshot.P50Ms.ShouldBe(20.0);

        // Mutating tracker after snapshot does not change the snapshot
        tracker.RecordLatency(1000.0);
        snapshot.MaxMs.ShouldBe(30.0);
        snapshot.SampleCount.ShouldBe(3);
    }

    [Fact]
    public void MaxSamples_EvictsOldest()
    {
        var tracker = new ServiceLatencyTracker(maxSamples: 5);

        for (var i = 1; i <= 10; i++)
            tracker.RecordLatency(i);

        // Only the last 5 samples should remain (6, 7, 8, 9, 10)
        tracker.SampleCount.ShouldBe(5);
        // TotalRequests counts all recorded calls, not just retained ones
        tracker.TotalRequests.ShouldBe(10L);
        // Minimum of retained samples is 6
        tracker.MinLatencyMs.ShouldBe(6.0);
        // Maximum of retained samples is 10
        tracker.MaxLatencyMs.ShouldBe(10.0);
    }

    [Fact]
    public void Account_RecordServiceLatency_DelegatesToTracker()
    {
        var account = new Account("test");

        account.RecordServiceLatency(50.0);
        account.RecordServiceLatency(100.0);

        account.LatencyTracker.TotalRequests.ShouldBe(2L);
        account.LatencyTracker.AverageLatencyMs.ShouldBe(75.0);
    }
}
