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

namespace NATS.Server.Tests;

public class RouteHandshakeTests
{
    [Fact]
    public async Task Two_servers_establish_route_connection()
    {
        await using var a = await TestServerFactory.CreateClusterEnabledAsync();
        await using var b = await TestServerFactory.CreateClusterEnabledAsync(seed: a.ClusterListen);

        await a.WaitForReadyAsync();
        await b.WaitForReadyAsync();

        using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
        while (!timeout.IsCancellationRequested && (a.Stats.Routes == 0 || b.Stats.Routes == 0))
        {
            await Task.Delay(50, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
        }

        a.Stats.Routes.ShouldBeGreaterThan(0);
        b.Stats.Routes.ShouldBeGreaterThan(0);
    }
}

internal static class TestServerFactory
{
    public static async Task<ClusterTestServer> CreateClusterEnabledAsync(string? seed = null)
    {
        var options = new NatsOptions
        {
            Host = "127.0.0.1",
            Port = 0,
            Cluster = new ClusterOptions
            {
                Name = Guid.NewGuid().ToString("N"),
                Host = "127.0.0.1",
                Port = 0,
                Routes = seed is null ? [] : [seed],
            },
        };

        var server = new NatsServer(options, NullLoggerFactory.Instance);
        var cts = new CancellationTokenSource();
        _ = server.StartAsync(cts.Token);
        await server.WaitForReadyAsync();

        return new ClusterTestServer(server, cts);
    }

    public static async Task<ClusterTestServer> CreateWithGatewayAndLeafAsync()
    {
        var options = new NatsOptions
        {
            Host = "127.0.0.1",
            Port = 0,
            Gateway = new GatewayOptions
            {
                Name = "G1",
                Host = "127.0.0.1",
                Port = 0,
            },
            LeafNode = new LeafNodeOptions
            {
                Host = "127.0.0.1",
                Port = 0,
            },
        };

        var server = new NatsServer(options, NullLoggerFactory.Instance);
        var cts = new CancellationTokenSource();
        _ = server.StartAsync(cts.Token);
        await server.WaitForReadyAsync();

        return new ClusterTestServer(server, cts);
    }

    public static async Task<ClusterTestServer> CreateJetStreamEnabledAsync()
    {
        var options = new NatsOptions
        {
            Host = "127.0.0.1",
            Port = 0,
            JetStream = new JetStreamOptions
            {
                StoreDir = Path.Combine(Path.GetTempPath(), $"nats-js-{Guid.NewGuid():N}"),
                MaxMemoryStore = 1024 * 1024,
                MaxFileStore = 10 * 1024 * 1024,
            },
        };

        var server = new NatsServer(options, NullLoggerFactory.Instance);
        var cts = new CancellationTokenSource();
        _ = server.StartAsync(cts.Token);
        await server.WaitForReadyAsync();

        return new ClusterTestServer(server, cts);
    }
}

internal sealed class ClusterTestServer(NatsServer server, CancellationTokenSource cts) : IAsyncDisposable
{
    public ServerStats Stats => server.Stats;
    public string ClusterListen => server.ClusterListen!;

    public Task WaitForReadyAsync() => server.WaitForReadyAsync();

    public async ValueTask DisposeAsync()
    {
        await cts.CancelAsync();
        server.Dispose();
        cts.Dispose();
    }
}
