using ZB.MOM.WW.ScadaBridge.AuditLog.Site;
using ZB.MOM.WW.Audit;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;

namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Site;

/// <summary>
/// Bundle B (M2-T3) tests for <see cref="RingBufferFallback"/> — the
/// drop-oldest fallback used by <see cref="FallbackAuditWriter"/> when the
/// primary SQLite writer is throwing.
/// </summary>
public class RingBufferFallbackTests
{
    private static AuditEvent NewEvent(string? target = null)
    {
        return ScadaBridgeAuditEventFactory.Create(
            eventId: Guid.NewGuid(),
            occurredAtUtc: DateTime.UtcNow,
            channel: AuditChannel.ApiOutbound,
            kind: AuditKind.ApiCall,
            status: AuditStatus.Delivered,
            target: target);
    }

    [Fact]
    public async Task Enqueue_1025_Into_1024Cap_Ring_DropsOldest_AndRaisesOverflowOnce()
    {
        var ring = new RingBufferFallback(capacity: 1024);
        var overflowCount = 0;
        ring.RingBufferOverflowed += () => Interlocked.Increment(ref overflowCount);

        var events = Enumerable.Range(0, 1025).Select(i => NewEvent(target: i.ToString())).ToList();
        foreach (var e in events)
        {
            Assert.True(ring.TryEnqueue(e));
        }

        Assert.Equal(1, overflowCount);

        // The surviving 1024 are events[1..1024] (oldest dropped).
        var drained = new List<AuditEvent>();
        ring.Complete();
        await foreach (var e in ring.DrainAsync(CancellationToken.None))
        {
            drained.Add(e);
        }

        Assert.Equal(1024, drained.Count);
        Assert.Equal("1", drained[0].Target);
        Assert.Equal("1024", drained[^1].Target);
    }

    [Fact]
    public async Task DrainAsync_Yields_FIFO_Then_Completes_When_Empty()
    {
        var ring = new RingBufferFallback(capacity: 16);
        var enqueued = Enumerable.Range(0, 5).Select(i => NewEvent(target: i.ToString())).ToList();
        foreach (var e in enqueued)
        {
            Assert.True(ring.TryEnqueue(e));
        }

        ring.Complete();

        var drained = new List<AuditEvent>();
        await foreach (var e in ring.DrainAsync(CancellationToken.None))
        {
            drained.Add(e);
        }

        Assert.Equal(5, drained.Count);
        for (int i = 0; i < 5; i++)
        {
            Assert.Equal(i.ToString(), drained[i].Target);
        }
    }

    [Fact]
    public void TryEnqueue_AllSucceeds_ReturnsTrue()
    {
        var ring = new RingBufferFallback(capacity: 16);
        for (int i = 0; i < 8; i++)
        {
            Assert.True(ring.TryEnqueue(NewEvent()));
        }
    }
}
