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

namespace NATS.Server.Gateways.Tests.Gateways;

/// <summary>
/// Gateway configuration validation, options parsing, monitoring endpoint,
/// and server lifecycle tests.
/// Ported from golang/nats-server/server/gateway_test.go.
/// </summary>
public class GatewayConfigTests
{
    // ── GatewayOptions Defaults ─────────────────────────────────────────

    // Go: TestGatewayBasic server/gateway_test.go:399
    [Fact]
    public void Default_gateway_options_have_correct_defaults()
    {
        var options = new GatewayOptions();
        options.Name.ShouldBeNull();
        options.Host.ShouldBe("0.0.0.0");
        options.Port.ShouldBe(0);
        options.Remotes.ShouldNotBeNull();
        options.Remotes.Count.ShouldBe(0);
    }

    // Go: TestGatewayBasic server/gateway_test.go:399
    [Fact]
    public void Gateway_options_name_can_be_set()
    {
        var options = new GatewayOptions { Name = "CLUSTER-A" };
        options.Name.ShouldBe("CLUSTER-A");
    }

    // Go: TestGatewayBasic server/gateway_test.go:399
    [Fact]
    public void Gateway_options_host_can_be_set()
    {
        var options = new GatewayOptions { Host = "192.168.1.1" };
        options.Host.ShouldBe("192.168.1.1");
    }

    // Go: TestGatewayBasic server/gateway_test.go:399
    [Fact]
    public void Gateway_options_port_can_be_set()
    {
        var options = new GatewayOptions { Port = 7222 };
        options.Port.ShouldBe(7222);
    }

    // Go: TestGatewayBasic server/gateway_test.go:399
    [Fact]
    public void Gateway_options_remotes_can_be_set()
    {
        var options = new GatewayOptions
        {
            Remotes = ["127.0.0.1:7222", "127.0.0.1:7223"],
        };
        options.Remotes.Count.ShouldBe(2);
    }

    // ── NatsOptions Gateway Configuration ───────────────────────────────

    // Go: TestGatewayBasic server/gateway_test.go:399
    [Fact]
    public void NatsOptions_gateway_is_null_by_default()
    {
        var opts = new NatsOptions();
        opts.Gateway.ShouldBeNull();
    }

    // Go: TestGatewayBasic server/gateway_test.go:399
    [Fact]
    public void NatsOptions_gateway_can_be_assigned()
    {
        var opts = new NatsOptions
        {
            Gateway = new GatewayOptions
            {
                Name = "TestGW",
                Host = "127.0.0.1",
                Port = 7222,
            },
        };
        opts.Gateway.ShouldNotBeNull();
        opts.Gateway.Name.ShouldBe("TestGW");
    }

    // ── Config File Parsing ─────────────────────────────────────────────

    // Go: TestGatewayWithListenToAny server/gateway_test.go:834
    [Fact]
    public void Config_processor_parses_gateway_name()
    {
        var config = """
            gateway {
                name: "MY-GATEWAY"
            }
            """;
        var opts = ConfigProcessor.ProcessConfig(config);
        opts.Gateway.ShouldNotBeNull();
        opts.Gateway!.Name.ShouldBe("MY-GATEWAY");
    }

    // Go: TestGatewayWithListenToAny server/gateway_test.go:834
    [Fact]
    public void Config_processor_parses_gateway_listen()
    {
        var config = """
            gateway {
                name: "GW"
                listen: "127.0.0.1:7222"
            }
            """;
        var opts = ConfigProcessor.ProcessConfig(config);
        opts.Gateway.ShouldNotBeNull();
        opts.Gateway!.Host.ShouldBe("127.0.0.1");
        opts.Gateway!.Port.ShouldBe(7222);
    }

    // Go: TestGatewayWithListenToAny server/gateway_test.go:834
    [Fact]
    public void Config_processor_parses_gateway_listen_any()
    {
        var config = """
            gateway {
                name: "GW"
                listen: "0.0.0.0:7333"
            }
            """;
        var opts = ConfigProcessor.ProcessConfig(config);
        opts.Gateway.ShouldNotBeNull();
        opts.Gateway!.Host.ShouldBe("0.0.0.0");
        opts.Gateway!.Port.ShouldBe(7333);
    }

    // Go: TestGatewayWithListenToAny server/gateway_test.go:834
    [Fact]
    public void Config_processor_gateway_without_name_leaves_null()
    {
        var config = """
            gateway {
                listen: "127.0.0.1:7222"
            }
            """;
        var opts = ConfigProcessor.ProcessConfig(config);
        opts.Gateway.ShouldNotBeNull();
        opts.Gateway!.Name.ShouldBeNull();
    }

    // Go: TestGatewayWithListenToAny server/gateway_test.go:834
    [Fact]
    public void Config_processor_no_gateway_section_leaves_null()
    {
        var config = """
            port: 4222
            """;
        var opts = ConfigProcessor.ProcessConfig(config);
        opts.Gateway.ShouldBeNull();
    }

    // ── Server Lifecycle with Gateway ───────────────────────────────────

    // Go: TestGatewayBasic server/gateway_test.go:399
    [Fact]
    public async Task Server_starts_with_gateway_configured()
    {
        var opts = new NatsOptions
        {
            Host = "127.0.0.1",
            Port = 0,
            Gateway = new GatewayOptions
            {
                Name = "LIFECYCLE",
                Host = "127.0.0.1",
                Port = 0,
            },
        };

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

        server.GatewayListen.ShouldNotBeNull();
        server.GatewayListen.ShouldContain("127.0.0.1:");

        await cts.CancelAsync();
        server.Dispose();
        cts.Dispose();
    }

    // Go: TestGatewayBasic server/gateway_test.go:399
    [Fact]
    public async Task Server_gateway_listen_uses_ephemeral_port()
    {
        var opts = new NatsOptions
        {
            Host = "127.0.0.1",
            Port = 0,
            Gateway = new GatewayOptions
            {
                Name = "EPHEMERAL",
                Host = "127.0.0.1",
                Port = 0,
            },
        };

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

        // The gateway listen should have a non-zero port
        var parts = server.GatewayListen!.Split(':');
        int.Parse(parts[1]).ShouldBeGreaterThan(0);

        await cts.CancelAsync();
        server.Dispose();
        cts.Dispose();
    }

    // Go: TestGatewayBasic server/gateway_test.go:399
    [Fact]
    public async Task Server_without_gateway_has_null_gateway_listen()
    {
        var opts = new NatsOptions
        {
            Host = "127.0.0.1",
            Port = 0,
        };

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

        server.GatewayListen.ShouldBeNull();

        await cts.CancelAsync();
        server.Dispose();
        cts.Dispose();
    }

    // Go: TestGatewayNoPanicOnStartupWithMonitoring server/gateway_test.go:6903
    [Fact]
    public async Task Server_starts_with_both_gateway_and_monitoring()
    {
        var opts = new NatsOptions
        {
            Host = "127.0.0.1",
            Port = 0,
            MonitorPort = 0,
            Gateway = new GatewayOptions
            {
                Name = "MON-GW",
                Host = "127.0.0.1",
                Port = 0,
            },
        };

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

        server.GatewayListen.ShouldNotBeNull();

        await cts.CancelAsync();
        server.Dispose();
        cts.Dispose();
    }

    // ── GatewayManager Unit Tests ───────────────────────────────────────

    // Go: TestGatewayBasic server/gateway_test.go:399
    [Fact]
    public async Task Gateway_manager_starts_and_listens()
    {
        var options = new GatewayOptions
        {
            Name = "UNIT",
            Host = "127.0.0.1",
            Port = 0,
        };
        var stats = new ServerStats();
        var manager = new GatewayManager(
            options,
            stats,
            "SERVER-1",
            _ => { },
            _ => { },
            NullLogger<GatewayManager>.Instance);

        await manager.StartAsync(CancellationToken.None);

        manager.ListenEndpoint.ShouldContain("127.0.0.1:");

        await manager.DisposeAsync();
    }

    // Go: TestGatewayBasic server/gateway_test.go:399
    [Fact]
    public async Task Gateway_manager_ephemeral_port_resolves()
    {
        var options = new GatewayOptions
        {
            Name = "UNIT",
            Host = "127.0.0.1",
            Port = 0,
        };
        var manager = new GatewayManager(
            options,
            new ServerStats(),
            "S1",
            _ => { },
            _ => { },
            NullLogger<GatewayManager>.Instance);

        await manager.StartAsync(CancellationToken.None);

        // Port should have been resolved
        options.Port.ShouldBeGreaterThan(0);

        await manager.DisposeAsync();
    }

    // Go: TestGatewayBasic server/gateway_test.go:399
    [Fact]
    public async Task Gateway_manager_dispose_decrements_stats()
    {
        var options = new GatewayOptions
        {
            Name = "STATS",
            Host = "127.0.0.1",
            Port = 0,
        };
        var stats = new ServerStats();
        var manager = new GatewayManager(
            options,
            stats,
            "S1",
            _ => { },
            _ => { },
            NullLogger<GatewayManager>.Instance);

        await manager.StartAsync(CancellationToken.None);
        await manager.DisposeAsync();

        stats.Gateways.ShouldBe(0);
    }

    // Go: TestGatewayBasic server/gateway_test.go:399
    [Fact]
    public async Task Gateway_manager_forward_without_connections_does_not_throw()
    {
        var options = new GatewayOptions
        {
            Name = "EMPTY",
            Host = "127.0.0.1",
            Port = 0,
        };
        var manager = new GatewayManager(
            options,
            new ServerStats(),
            "S1",
            _ => { },
            _ => { },
            NullLogger<GatewayManager>.Instance);

        // ForwardMessageAsync without any connections should not throw
        await manager.ForwardMessageAsync("$G", "test", null, new byte[] { 1 }, CancellationToken.None);

        manager.ForwardedJetStreamClusterMessages.ShouldBe(0);
    }

    // Go: TestGatewayBasic server/gateway_test.go:399
    [Fact]
    public async Task Gateway_manager_propagate_without_connections_does_not_throw()
    {
        var options = new GatewayOptions
        {
            Name = "EMPTY",
            Host = "127.0.0.1",
            Port = 0,
        };
        var manager = new GatewayManager(
            options,
            new ServerStats(),
            "S1",
            _ => { },
            _ => { },
            NullLogger<GatewayManager>.Instance);

        // These should not throw even without connections
        manager.PropagateLocalSubscription("$G", "test.>", null);
        manager.PropagateLocalUnsubscription("$G", "test.>", null);
    }

    // ── GatewayzHandler ─────────────────────────────────────────────────

    // Go: TestGatewayNoPanicOnStartupWithMonitoring server/gateway_test.go:6903
    [Fact]
    public async Task Gatewayz_handler_returns_gateway_count()
    {
        await using var fixture = await GatewayConfigFixture.StartAsync();

        var handler = new GatewayzHandler(fixture.Local);
        var result = handler.Build();
        result.ShouldNotBeNull();
    }

    // Go: TestGatewayNoPanicOnStartupWithMonitoring server/gateway_test.go:6903
    [Fact]
    public async Task Gatewayz_handler_reflects_active_connections()
    {
        await using var fixture = await GatewayConfigFixture.StartAsync();

        fixture.Local.Stats.Gateways.ShouldBeGreaterThan(0);
    }

    // ── Duplicate Remote Deduplication ───────────────────────────────────

    // Go: TestGatewayBasic server/gateway_test.go:399
    [Fact]
    public async Task Duplicate_remotes_are_deduplicated()
    {
        var localOptions = new NatsOptions
        {
            Host = "127.0.0.1",
            Port = 0,
            Gateway = new GatewayOptions
            {
                Name = "LOCAL",
                Host = "127.0.0.1",
                Port = 0,
            },
        };

        var local = new NatsServer(localOptions, NullLoggerFactory.Instance);
        var localCts = new CancellationTokenSource();
        _ = local.StartAsync(localCts.Token);
        await local.WaitForReadyAsync();

        // Create remote with duplicate entries
        var remoteOptions = new NatsOptions
        {
            Host = "127.0.0.1",
            Port = 0,
            Gateway = new GatewayOptions
            {
                Name = "REMOTE",
                Host = "127.0.0.1",
                Port = 0,
                Remotes = [local.GatewayListen!, local.GatewayListen!],
            },
        };

        var remote = new NatsServer(remoteOptions, NullLoggerFactory.Instance);
        var remoteCts = new CancellationTokenSource();
        _ = remote.StartAsync(remoteCts.Token);
        await remote.WaitForReadyAsync();

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

        // Should have exactly 1 gateway connection, not 2
        // (remote deduplicates identical endpoints)
        local.Stats.Gateways.ShouldBeGreaterThan(0);
        remote.Stats.Gateways.ShouldBeGreaterThan(0);

        await localCts.CancelAsync();
        await remoteCts.CancelAsync();
        local.Dispose();
        remote.Dispose();
        localCts.Dispose();
        remoteCts.Dispose();
    }

    // ── ServerStats Gateway Fields ──────────────────────────────────────

    // Go: TestGatewayBasic server/gateway_test.go:399
    [Fact]
    public void ServerStats_gateway_fields_initialized_to_zero()
    {
        var stats = new ServerStats();
        stats.Gateways.ShouldBe(0);
        stats.SlowConsumerGateways.ShouldBe(0);
        stats.StaleConnectionGateways.ShouldBe(0);
    }

    // Go: TestGatewaySlowConsumer server/gateway_test.go:7003
    [Fact]
    public void ServerStats_gateway_counter_atomic()
    {
        var stats = new ServerStats();
        Interlocked.Increment(ref stats.Gateways);
        Interlocked.Increment(ref stats.Gateways);
        stats.Gateways.ShouldBe(2);
        Interlocked.Decrement(ref stats.Gateways);
        stats.Gateways.ShouldBe(1);
    }
}

/// <summary>
/// Shared fixture for config tests.
/// </summary>
internal sealed class GatewayConfigFixture : IAsyncDisposable
{
    private readonly CancellationTokenSource _localCts;
    private readonly CancellationTokenSource _remoteCts;

    private GatewayConfigFixture(NatsServer local, NatsServer remote, CancellationTokenSource localCts, CancellationTokenSource remoteCts)
    {
        Local = local;
        Remote = remote;
        _localCts = localCts;
        _remoteCts = remoteCts;
    }

    public NatsServer Local { get; }
    public NatsServer Remote { get; }

    public static async Task<GatewayConfigFixture> StartAsync()
    {
        var localOptions = new NatsOptions
        {
            Host = "127.0.0.1",
            Port = 0,
            Gateway = new GatewayOptions
            {
                Name = "LOCAL",
                Host = "127.0.0.1",
                Port = 0,
            },
        };

        var local = new NatsServer(localOptions, NullLoggerFactory.Instance);
        var localCts = new CancellationTokenSource();
        _ = local.StartAsync(localCts.Token);
        await local.WaitForReadyAsync();

        var remoteOptions = new NatsOptions
        {
            Host = "127.0.0.1",
            Port = 0,
            Gateway = new GatewayOptions
            {
                Name = "REMOTE",
                Host = "127.0.0.1",
                Port = 0,
                Remotes = [local.GatewayListen!],
            },
        };

        var remote = new NatsServer(remoteOptions, NullLoggerFactory.Instance);
        var remoteCts = new CancellationTokenSource();
        _ = remote.StartAsync(remoteCts.Token);
        await remote.WaitForReadyAsync();

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

        return new GatewayConfigFixture(local, remote, localCts, remoteCts);
    }

    public async ValueTask DisposeAsync()
    {
        await _localCts.CancelAsync();
        await _remoteCts.CancelAsync();
        Local.Dispose();
        Remote.Dispose();
        _localCts.Dispose();
        _remoteCts.Dispose();
    }
}
