using System.Net.Http.Json;
using NATS.Client.Core;

namespace NATS.E2E.Tests.Infrastructure;

public sealed class LeafNodeFixture : IAsyncLifetime
{
    private NatsServerProcess _hub = null!;
    private NatsServerProcess _leaf = null!;

    public int HubPort => _hub.Port;
    public int LeafPort => _leaf.Port;

    public async Task InitializeAsync()
    {
        var leafListenPort = NatsServerProcess.AllocateFreePort();

        var hubConfig = $$"""
            server_name: hub
            leafnodes {
                listen: 127.0.0.1:{{leafListenPort}}
            }
            """;

        _hub = NatsServerProcess.WithConfig(hubConfig, enableMonitoring: true);
        await _hub.StartAsync();

        var leafConfig = $$"""
            server_name: leaf
            leafnodes {
                remotes [
                    { url: "nats-leaf://127.0.0.1:{{leafListenPort}}" }
                ]
            }
            """;

        _leaf = NatsServerProcess.WithConfig(leafConfig);
        await _leaf.StartAsync();

        // Poll hub's /leafz until it reports 1 connected leaf node
        await WaitForLeafConnectionAsync();
    }

    private async Task WaitForLeafConnectionAsync()
    {
        using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(2) };
        using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(30));
        using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(200));

        while (await timer.WaitForNextTickAsync(deadline.Token).ConfigureAwait(false))
        {
            try
            {
                var leafz = await http.GetFromJsonAsync<Leafz>(
                    $"http://127.0.0.1:{_hub.MonitorPort!.Value}/leafz",
                    deadline.Token);

                if (leafz?.NumLeafs >= 1)
                    return;
            }
            catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException
                                        && !deadline.IsCancellationRequested)
            {
                // Monitor not yet ready or per-request timeout — retry on next tick
                _ = ex;
            }
        }

        throw new TimeoutException("Leaf node did not connect to hub within 30s.");
    }

    public async Task DisposeAsync()
    {
        await _leaf.DisposeAsync();
        await _hub.DisposeAsync();
    }

    public NatsConnection CreateHubClient()
        => new(new NatsOpts { Url = $"nats://127.0.0.1:{HubPort}" });

    public NatsConnection CreateLeafClient()
        => new(new NatsOpts { Url = $"nats://127.0.0.1:{LeafPort}" });

    private sealed class Leafz
    {
        [System.Text.Json.Serialization.JsonPropertyName("num_leafs")]
        public int NumLeafs { get; init; }
    }
}

[CollectionDefinition("E2E-LeafNode")]
public class LeafNodeCollection : ICollectionFixture<LeafNodeFixture>;
