using NATS.Client.Core;
using NATS.E2E.Tests.Infrastructure;

namespace NATS.E2E.Tests;

[Collection("E2E-Accounts")]
public class AccountIsolationTests(AccountServerFixture fixture)
{
    [Fact]
    public async Task Accounts_SameAccount_MessageDelivered()
    {
        await using var pub = fixture.CreateClientA();
        await using var sub = fixture.CreateClientA();

        await pub.ConnectAsync();
        await sub.ConnectAsync();

        await using var subscription = await sub.SubscribeCoreAsync<string>("acct.test");
        await sub.PingAsync();

        await pub.PublishAsync("acct.test", "intra-account");

        using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
        var msg = await subscription.Msgs.ReadAsync(cts.Token);

        msg.Data.ShouldBe("intra-account");
    }

    [Fact]
    public async Task Accounts_CrossAccount_MessageNotDelivered()
    {
        await using var pub = fixture.CreateClientA();
        await using var sub = fixture.CreateClientB();

        await pub.ConnectAsync();
        await sub.ConnectAsync();

        await using var subscription = await sub.SubscribeCoreAsync<string>("cross.test");
        await sub.PingAsync();

        await pub.PublishAsync("cross.test", "cross-account");

        using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
        var readTask = subscription.Msgs.ReadAsync(cts.Token).AsTask();
        var completed = await Task.WhenAny(readTask, Task.Delay(1000));

        completed.ShouldNotBe(readTask);
    }

    [Fact]
    public async Task Accounts_EachAccountHasOwnNamespace()
    {
        await using var pubA = fixture.CreateClientA();
        await using var subA = fixture.CreateClientA();
        await using var pubB = fixture.CreateClientB();
        await using var subB = fixture.CreateClientB();

        await pubA.ConnectAsync();
        await subA.ConnectAsync();
        await pubB.ConnectAsync();
        await subB.ConnectAsync();

        await using var subscriptionA = await subA.SubscribeCoreAsync<string>("shared.topic");
        await using var subscriptionB = await subB.SubscribeCoreAsync<string>("shared.topic");

        await subA.PingAsync();
        await subB.PingAsync();

        // Publish from ACCT_A — only ACCT_A subscriber should receive
        await pubA.PublishAsync("shared.topic", "from-a");

        using var ctA = new CancellationTokenSource(TimeSpan.FromSeconds(10));
        var msgA = await subscriptionA.Msgs.ReadAsync(ctA.Token);
        msgA.Data.ShouldBe("from-a");

        using var ctsBNoMsg = new CancellationTokenSource(TimeSpan.FromSeconds(5));
        var readBTask = subscriptionB.Msgs.ReadAsync(ctsBNoMsg.Token).AsTask();
        var completedB = await Task.WhenAny(readBTask, Task.Delay(1000));
        completedB.ShouldNotBe(readBTask);
        // Cancel the abandoned read so it doesn't consume the next message
        await ctsBNoMsg.CancelAsync();
        try { await readBTask; } catch (OperationCanceledException) { }

        // Publish from ACCT_B — only ACCT_B subscriber should receive
        await pubB.PublishAsync("shared.topic", "from-b");

        using var ctB = new CancellationTokenSource(TimeSpan.FromSeconds(10));
        var msgB = await subscriptionB.Msgs.ReadAsync(ctB.Token);
        msgB.Data.ShouldBe("from-b");

        using var ctsANoMsg = new CancellationTokenSource(TimeSpan.FromSeconds(5));
        var readATask2 = subscriptionA.Msgs.ReadAsync(ctsANoMsg.Token).AsTask();
        var completedA2 = await Task.WhenAny(readATask2, Task.Delay(1000));
        completedA2.ShouldNotBe(readATask2);
        await ctsANoMsg.CancelAsync();
        try { await readATask2; } catch (OperationCanceledException) { }
    }
}
