using Microsoft.Extensions.Logging.Abstractions;
using NATS.Server.Configuration;

namespace NATS.Server.Gateways.Tests.Gateways;

public class GatewayConnectionDirectionParityBatch2Tests
{
    [Fact]
    public async Task Gateway_manager_tracks_inbound_and_outbound_connection_sets()
    {
        var a = await StartServerAsync(MakeGatewayOptions("GW-A"));
        var b = await StartServerAsync(MakeGatewayOptions("GW-B", a.Server.GatewayListen));

        try
        {
            await WaitForCondition(() =>
                a.Server.NumInboundGateways() == 1 &&
                b.Server.NumOutboundGateways() == 1,
                10000);

            a.Server.NumInboundGateways().ShouldBe(1);
            a.Server.NumOutboundGateways().ShouldBe(0);
            b.Server.NumOutboundGateways().ShouldBe(1);
            b.Server.NumInboundGateways().ShouldBe(0);

            var aManager = a.Server.GatewayManager;
            var bManager = b.Server.GatewayManager;
            aManager.ShouldNotBeNull();
            bManager.ShouldNotBeNull();

            aManager!.HasInbound(b.Server.ServerId).ShouldBeTrue();
            bManager!.HasInbound(a.Server.ServerId).ShouldBeFalse();

            bManager.GetOutboundGatewayConnection(a.Server.ServerId).ShouldNotBeNull();
            bManager.GetOutboundGatewayConnection("does-not-exist").ShouldBeNull();

            aManager.GetInboundGatewayConnections().Count.ShouldBe(1);
            aManager.GetOutboundGatewayConnections().Count.ShouldBe(0);
            bManager.GetOutboundGatewayConnections().Count.ShouldBe(1);
            bManager.GetInboundGatewayConnections().Count.ShouldBe(0);
        }
        finally
        {
            await DisposeServers(a, b);
        }
    }

    private static NatsOptions MakeGatewayOptions(string gatewayName, string? remote = null)
    {
        return new NatsOptions
        {
            Host = "127.0.0.1",
            Port = 0,
            Gateway = new GatewayOptions
            {
                Name = gatewayName,
                Host = "127.0.0.1",
                Port = 0,
                Remotes = remote is null ? [] : [remote],
            },
        };
    }

    private static async Task<(NatsServer Server, CancellationTokenSource Cts)> StartServerAsync(NatsOptions opts)
    {
        var server = new NatsServer(opts, NullLoggerFactory.Instance);
        var cts = new CancellationTokenSource();
        _ = server.StartAsync(cts.Token);
        await server.WaitForReadyAsync();
        return (server, cts);
    }

    private static async Task DisposeServers(params (NatsServer Server, CancellationTokenSource Cts)[] servers)
    {
        foreach (var (server, cts) in servers)
        {
            await cts.CancelAsync();
            server.Dispose();
            cts.Dispose();
        }
    }

    private static async Task WaitForCondition(Func<bool> predicate, int timeoutMs)
    {
        using var cts = new CancellationTokenSource(timeoutMs);
        while (!cts.IsCancellationRequested)
        {
            if (predicate())
                return;
            await Task.Yield();
        }

        throw new TimeoutException("Condition not met.");
    }
}
