using System.Text.Json.Nodes;
using Eis.Infrastructure.Caching;

namespace Eis.Infrastructure.Tests.Caching;

public sealed class ApplicationCacheTests
{
    [Fact]
    public async Task RememberJsonAsync_CoalescesConcurrentLocalLoads()
    {
        using var cache = new ApplicationCache(redisUrl: null);
        var calls = 0;
        var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);

        async Task<JsonObject> Load()
        {
            Interlocked.Increment(ref calls);
            await release.Task;
            return new JsonObject { ["value"] = 42 };
        }

        var first = cache.RememberJsonAsync("public", "home", Load);
        var second = cache.RememberJsonAsync("public", "home", Load);
        release.SetResult();

        var values = await Task.WhenAll(first, second);

        Assert.Equal(1, calls);
        Assert.All(values, value => Assert.Equal(42, value["value"]!.GetValue<int>()));
    }

    [Fact]
    public async Task InvalidateAsync_DropsLocalNamespaceWithoutAffectingOthers()
    {
        using var cache = new ApplicationCache(redisUrl: null);
        var publicCalls = 0;
        var resultCalls = 0;

        Task<JsonObject> PublicLoader() =>
            Task.FromResult(new JsonObject { ["generation"] = ++publicCalls });
        Task<JsonObject> ResultLoader() =>
            Task.FromResult(new JsonObject { ["generation"] = ++resultCalls });

        _ = await cache.RememberJsonAsync("public", "home", PublicLoader);
        _ = await cache.RememberJsonAsync("results", "candidate:1", ResultLoader);
        Assert.False(await cache.InvalidateAsync("public"));
        var publicValue = await cache.RememberJsonAsync("public", "home", PublicLoader);
        var resultValue = await cache.RememberJsonAsync("results", "candidate:1", ResultLoader);

        Assert.Equal(2, publicValue["generation"]!.GetValue<int>());
        Assert.Equal(1, resultValue["generation"]!.GetValue<int>());
    }

    [Fact]
    public async Task InvalidateAsync_PreventsInFlightLoadFromRepopulatingOldGeneration()
    {
        using var cache = new ApplicationCache(redisUrl: null);
        var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        var calls = 0;
        async Task<JsonObject> SlowLoader()
        {
            var value = Interlocked.Increment(ref calls);
            await release.Task;
            return new JsonObject { ["generation"] = value };
        }

        var staleLoad = cache.RememberJsonAsync("public", "home", SlowLoader);
        _ = await cache.InvalidateAsync("public");
        var refreshed = await cache.RememberJsonAsync(
            "public",
            "home",
            () => Task.FromResult(new JsonObject { ["generation"] = Interlocked.Increment(ref calls) }));
        release.SetResult();
        _ = await staleLoad;

        Assert.Equal(2, refreshed["generation"]!.GetValue<int>());
    }
}
