using System.Diagnostics;
using NATS.Server.Benchmark.Tests.Harness;
using NATS.Server.Subscriptions;
using Xunit.Abstractions;

namespace NATS.Server.Benchmark.Tests.CorePubSub;

public class SubListMatchBenchmarks(ITestOutputHelper output)
{
    [Fact]
    [Trait("Category", "Benchmark")]
    public void SubListExactMatch_128Subjects()
    {
        using var subList = new SubList();
        for (var i = 0; i < 128; i++)
            subList.Insert(new Subscription { Subject = $"bench.exact.{i}", Sid = i.ToString() });

        var (result, allocatedBytes) = Measure("SubList Exact Match (128 subjects)", "DotNet", "bench.exact.64".Length, 250_000, () =>
        {
            _ = subList.Match("bench.exact.64");
        });

        BenchmarkResultWriter.WriteSingle(output, result);
        WriteAllocationSummary(allocatedBytes, result.TotalMessages);
    }

    [Fact]
    [Trait("Category", "Benchmark")]
    public void SubListWildcardMatch_FanIn()
    {
        using var subList = new SubList();
        subList.Insert(new Subscription { Subject = "orders.created", Sid = "1" });
        subList.Insert(new Subscription { Subject = "orders.*", Sid = "2" });
        subList.Insert(new Subscription { Subject = "orders.>", Sid = "3" });
        subList.Insert(new Subscription { Subject = "orders.created.us", Sid = "4" });

        var (result, allocatedBytes) = Measure("SubList Wildcard Match", "DotNet", "orders.created".Length, 250_000, () =>
        {
            _ = subList.Match("orders.created");
        });

        BenchmarkResultWriter.WriteSingle(output, result);
        WriteAllocationSummary(allocatedBytes, result.TotalMessages);
    }

    [Fact]
    [Trait("Category", "Benchmark")]
    public void SubListQueueMatch_MergedGroups()
    {
        using var subList = new SubList();
        subList.Insert(new Subscription { Subject = "jobs.run", Queue = "workers", Sid = "1" });
        subList.Insert(new Subscription { Subject = "jobs.*", Queue = "workers", Sid = "2" });
        subList.Insert(new Subscription { Subject = "jobs.>", Queue = "audit", Sid = "3" });

        var (result, allocatedBytes) = Measure("SubList Queue Match", "DotNet", "jobs.run".Length, 250_000, () =>
        {
            _ = subList.Match("jobs.run");
        });

        BenchmarkResultWriter.WriteSingle(output, result);
        WriteAllocationSummary(allocatedBytes, result.TotalMessages);
    }

    [Fact]
    [Trait("Category", "Benchmark")]
    public void SubListRemoteInterest_WildcardLookup()
    {
        using var subList = new SubList();
        for (var i = 0; i < 64; i++)
            subList.ApplyRemoteSub(new RemoteSubscription($"remote.{i}.*", null, $"r{i}", "A"));

        var (result, allocatedBytes) = Measure("SubList Remote Interest", "DotNet", "remote.42.created".Length, 250_000, () =>
        {
            _ = subList.HasRemoteInterest("A", "remote.42.created");
        });

        BenchmarkResultWriter.WriteSingle(output, result);
        WriteAllocationSummary(allocatedBytes, result.TotalMessages);
    }

    private static (BenchmarkResult Result, long AllocatedBytes) Measure(string name, string serverType, int bytesPerOperation, int iterations, Action operation)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();

        for (var i = 0; i < 1_000; i++)
            operation();

        var before = GC.GetAllocatedBytesForCurrentThread();
        var sw = Stopwatch.StartNew();
        for (var i = 0; i < iterations; i++)
            operation();
        sw.Stop();
        var allocatedBytes = GC.GetAllocatedBytesForCurrentThread() - before;

        return (new BenchmarkResult
        {
            Name = name,
            ServerType = serverType,
            TotalMessages = iterations,
            TotalBytes = (long)iterations * bytesPerOperation,
            Duration = sw.Elapsed,
        }, allocatedBytes);
    }

    private void WriteAllocationSummary(long allocatedBytes, long iterations)
    {
        output.WriteLine($"Allocated: {allocatedBytes:N0} B total | {allocatedBytes / (double)iterations:F2} B/op");
        output.WriteLine("");
    }
}
