using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using SatelliteProvider.Common.DTO;
using SatelliteProvider.Services.RegionProcessing;

namespace SatelliteProvider.Tests;

public class RegionRequestQueueTests
{
    private static RegionRequest BuildRequest() => new()
    {
        Id = Guid.NewGuid(),
        Latitude = 47.461747,
        Longitude = 37.647063,
        SizeMeters = 200,
        ZoomLevel = 18,
        StitchTiles = false
    };

    [Fact]
    public async Task EnqueueAsync_RespectsCapacity_WritesUpToCapacityWithoutBlocking_RS04()
    {
        const int capacity = 10;
        var queue = new RegionRequestQueue(capacity, NullLogger<RegionRequestQueue>.Instance);

        for (var i = 0; i < capacity; i++)
        {
            await queue.EnqueueAsync(BuildRequest());
        }

        queue.Count.Should().Be(capacity);
    }

    [Fact]
    public async Task EnqueueAsync_BlocksWhenAtCapacity_UntilDequeue_RL02()
    {
        const int capacity = 2;
        var queue = new RegionRequestQueue(capacity, NullLogger<RegionRequestQueue>.Instance);

        await queue.EnqueueAsync(BuildRequest());
        await queue.EnqueueAsync(BuildRequest());

        var overflow = queue.EnqueueAsync(BuildRequest()).AsTask();
        var completedFirst = await Task.WhenAny(overflow, Task.Delay(150));

        completedFirst.Should().NotBeSameAs(overflow, "overflow enqueue must wait while queue is full");

        var dequeued = await queue.DequeueAsync();
        dequeued.Should().NotBeNull();

        await overflow.WaitAsync(TimeSpan.FromSeconds(1));
        queue.Count.Should().Be(capacity);
    }

    [Fact]
    public async Task EnqueueAsync_HonorsCancellation_WhenFull()
    {
        var queue = new RegionRequestQueue(1, NullLogger<RegionRequestQueue>.Instance);
        await queue.EnqueueAsync(BuildRequest());

        using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(150));

        Func<Task> act = async () => await queue.EnqueueAsync(BuildRequest(), cts.Token);

        await act.Should().ThrowAsync<OperationCanceledException>();
    }

    [Fact]
    public async Task DequeueAsync_ReturnsItemsInFifoOrder()
    {
        var queue = new RegionRequestQueue(8, NullLogger<RegionRequestQueue>.Instance);
        var first = BuildRequest();
        var second = BuildRequest();

        await queue.EnqueueAsync(first);
        await queue.EnqueueAsync(second);

        var dequeued1 = await queue.DequeueAsync();
        var dequeued2 = await queue.DequeueAsync();

        dequeued1!.Id.Should().Be(first.Id);
        dequeued2!.Id.Should().Be(second.Id);
        queue.Count.Should().Be(0);
    }
}
