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

namespace NATS.E2E.Tests.Infrastructure;

public sealed class GatewayFixture : IAsyncLifetime
{
    private NatsServerProcess _serverA = null!;
    private NatsServerProcess _serverB = null!;

    public int PortA => _serverA.Port;
    public int PortB => _serverB.Port;

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

        var configA = $$"""
            server_name: gw-a
            gateway {
                name: cluster-a
                listen: 127.0.0.1:{{gwPortA}}
                gateways: [
                    { name: cluster-b, url: nats://127.0.0.1:{{gwPortB}} }
                ]
            }
            """;

        var configB = $$"""
            server_name: gw-b
            gateway {
                name: cluster-b
                listen: 127.0.0.1:{{gwPortB}}
                gateways: [
                    { name: cluster-a, url: nats://127.0.0.1:{{gwPortA}} }
                ]
            }
            """;

        _serverA = NatsServerProcess.WithConfig(configA, enableMonitoring: true);
        _serverB = NatsServerProcess.WithConfig(configB, enableMonitoring: true);

        await Task.WhenAll(_serverA.StartAsync(), _serverB.StartAsync());

        // Poll until both gateways report a connected outbound gateway
        await WaitForGatewayConnectionAsync();
    }

    private async Task WaitForGatewayConnectionAsync()
    {
        using var http = new HttpClient();
        using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30));
        using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(200));

        var monitorPorts = new[] { _serverA.MonitorPort!.Value, _serverB.MonitorPort!.Value };

        while (await timer.WaitForNextTickAsync(timeout.Token).ConfigureAwait(false))
        {
            var allConnected = true;
            foreach (var monitorPort in monitorPorts)
            {
                try
                {
                    var gatewayz = await http.GetFromJsonAsync<Gatewayz>(
                        $"http://127.0.0.1:{monitorPort}/gatewayz",
                        timeout.Token);

                    if (gatewayz?.NumGateways < 1)
                    {
                        allConnected = false;
                        break;
                    }
                }
                catch (HttpRequestException)
                {
                    // monitor not yet ready — retry on next tick
                    allConnected = false;
                    break;
                }
            }

            if (allConnected)
                return;
        }

        throw new TimeoutException("Gateways did not connect to each other within 30s.");
    }

    public async Task DisposeAsync()
    {
        await Task.WhenAll(
            _serverA.DisposeAsync().AsTask(),
            _serverB.DisposeAsync().AsTask());
    }

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

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

    private sealed class Gatewayz
    {
        [System.Text.Json.Serialization.JsonPropertyName("num_gateways")]
        public int NumGateways { get; init; }
    }
}

[CollectionDefinition("E2E-Gateway")]
public class GatewayCollection : ICollectionFixture<GatewayFixture>;
