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

namespace NATS.Server.Gateways.Tests.Gateways;

/// <summary>
/// Tests for gateway connection registration and state tracking (Gap 11.7).
/// Go reference: server/gateway.go srvGateway / outboundGateway struct and gwConnState transitions.
/// </summary>
public class GatewayRegistrationTests
{
    // Go: server/gateway.go solicitGateway creates an outbound entry before dialling.
    [Fact]
    public void RegisterGateway_creates_registration()
    {
        var manager = BuildManager();

        manager.RegisterGateway("gw-east");

        manager.GetRegistration("gw-east").ShouldNotBeNull();
    }

    // Go: server/gateway.go initial gwConnState is gwConnecting before the dial completes.
    [Fact]
    public void RegisterGateway_starts_in_connecting_state()
    {
        var manager = BuildManager();

        manager.RegisterGateway("gw-east");
        var reg = manager.GetRegistration("gw-east")!;

        reg.State.ShouldBe(GatewayConnectionState.Connecting);
    }

    // Go: server/gateway.go gwConnState transitions (Connecting → Connected, Connected → Disconnected, etc.).
    [Fact]
    public void UpdateState_changes_state()
    {
        var manager = BuildManager();
        manager.RegisterGateway("gw-east");

        manager.UpdateState("gw-east", GatewayConnectionState.Connected);

        manager.GetRegistration("gw-east")!.State.ShouldBe(GatewayConnectionState.Connected);
    }

    // Go: server/gateway.go getOutboundGatewayConnection returns nil for unknown names.
    [Fact]
    public void GetRegistration_returns_null_for_unknown()
    {
        var manager = BuildManager();

        manager.GetRegistration("does-not-exist").ShouldBeNull();
    }

    // Go: server/gateway.go server.gateways map stores all configured outbound gateways.
    [Fact]
    public void GetAllRegistrations_returns_all()
    {
        var manager = BuildManager();
        manager.RegisterGateway("gw-east");
        manager.RegisterGateway("gw-west");

        var all = manager.GetAllRegistrations();

        all.Count.ShouldBe(2);
        all.Select(r => r.Name).ShouldContain("gw-east");
        all.Select(r => r.Name).ShouldContain("gw-west");
    }

    // Go: server/gateway.go outboundGateway teardown removes entry from server.gateways.
    [Fact]
    public void UnregisterGateway_removes_registration()
    {
        var manager = BuildManager();
        manager.RegisterGateway("gw-east");

        manager.UnregisterGateway("gw-east");

        manager.GetRegistration("gw-east").ShouldBeNull();
    }

    // Go: server/gateway.go numOutboundGatewayConnections counts only fully-connected entries.
    [Fact]
    public void GetConnectedGatewayCount_counts_connected_only()
    {
        var manager = BuildManager();
        manager.RegisterGateway("gw-east");   // Connecting
        manager.RegisterGateway("gw-west");   // Connecting
        manager.RegisterGateway("gw-south");  // → Connected

        manager.UpdateState("gw-south", GatewayConnectionState.Connected);

        manager.GetConnectedGatewayCount().ShouldBe(1);
    }

    // Go: server/gateway.go outboundGateway.msgs.outMsgs incremented per forwarded message.
    [Fact]
    public void IncrementMessagesSent_increments()
    {
        var manager = BuildManager();
        manager.RegisterGateway("gw-east");

        manager.IncrementMessagesSent("gw-east");
        manager.IncrementMessagesSent("gw-east");
        manager.IncrementMessagesSent("gw-east");

        manager.GetRegistration("gw-east")!.MessagesSent.ShouldBe(3L);
    }

    // Go: server/gateway.go inboundGateway.msgs.inMsgs incremented per received message.
    [Fact]
    public void IncrementMessagesReceived_increments()
    {
        var manager = BuildManager();
        manager.RegisterGateway("gw-east");

        manager.IncrementMessagesReceived("gw-east");
        manager.IncrementMessagesReceived("gw-east");

        manager.GetRegistration("gw-east")!.MessagesReceived.ShouldBe(2L);
    }

    // Go: server/gateway.go outboundGateway.remoteName / remoteIP stored for monitoring.
    [Fact]
    public void Registration_stores_remote_address()
    {
        var manager = BuildManager();

        manager.RegisterGateway("gw-east", remoteAddress: "10.0.0.1:7222");

        manager.GetRegistration("gw-east")!.RemoteAddress.ShouldBe("10.0.0.1:7222");
    }

    // Go: server/gateway.go gwConnState Connected transition stamps connected-at time.
    [Fact]
    public void UpdateState_to_connected_stamps_ConnectedAtUtc()
    {
        var manager = BuildManager();
        manager.RegisterGateway("gw-east");
        var before = DateTime.UtcNow;

        manager.UpdateState("gw-east", GatewayConnectionState.Connected);

        var after = DateTime.UtcNow;
        var stamp = manager.GetRegistration("gw-east")!.ConnectedAtUtc;
        stamp.ShouldBeGreaterThanOrEqualTo(before);
        stamp.ShouldBeLessThanOrEqualTo(after);
    }

    // ── Helpers ──────────────────────────────────────────────────────────────

    private static GatewayManager BuildManager() =>
        new GatewayManager(
            new GatewayOptions { Name = "TEST", Host = "127.0.0.1", Port = 0 },
            new ServerStats(),
            "S1",
            _ => { },
            _ => { },
            NullLogger<GatewayManager>.Instance);
}
