using System.Net;
using System.Net.Sockets;
using NATS.Server.Gateways;
using Shouldly;

namespace NATS.Server.Gateways.Tests.Gateways;

/// <summary>
/// Tests for queue group subscription tracking on GatewayConnection.
/// Go reference: gateway.go — sendQueueSubsToGateway, queueSubscriptions state.
/// </summary>
public class QueueGroupPropagationTests : IAsyncDisposable
{
    private readonly TcpListener _listener;
    private readonly Socket _clientSocket;
    private readonly Socket _serverSocket;
    private readonly GatewayConnection _gw;

    public QueueGroupPropagationTests()
    {
        _listener = new TcpListener(IPAddress.Loopback, 0);
        _listener.Start();
        var port = ((IPEndPoint)_listener.LocalEndpoint).Port;

        _clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        _clientSocket.Connect(IPAddress.Loopback, port);
        _serverSocket = _listener.AcceptSocket();

        _gw = new GatewayConnection(_serverSocket);
    }

    public async ValueTask DisposeAsync()
    {
        await _gw.DisposeAsync();
        _clientSocket.Dispose();
        _listener.Stop();
    }

    // Go: gateway.go — sendQueueSubsToGateway registers queue group subscriptions per subject
    [Fact]
    public void AddQueueSubscription_registers_group()
    {
        _gw.AddQueueSubscription("orders.new", "workers");

        _gw.HasQueueSubscription("orders.new", "workers").ShouldBeTrue();
    }

    // Go: gateway.go — getQueueGroups returns all groups for a subject
    [Fact]
    public void GetQueueGroups_returns_groups_for_subject()
    {
        _gw.AddQueueSubscription("payments.>", "billing");
        _gw.AddQueueSubscription("payments.>", "audit");

        var groups = _gw.GetQueueGroups("payments.>");

        groups.ShouldContain("billing");
        groups.ShouldContain("audit");
        groups.Count.ShouldBe(2);
    }

    // Go: gateway.go — removeQueueSubscription removes a specific queue group
    [Fact]
    public void RemoveQueueSubscription_removes_group()
    {
        _gw.AddQueueSubscription("events.click", "analytics");
        _gw.AddQueueSubscription("events.click", "logging");

        _gw.RemoveQueueSubscription("events.click", "analytics");

        _gw.HasQueueSubscription("events.click", "analytics").ShouldBeFalse();
        _gw.HasQueueSubscription("events.click", "logging").ShouldBeTrue();
    }

    // Go: gateway.go — hasQueueSubscription returns true for registered subject/group pair
    [Fact]
    public void HasQueueSubscription_true_when_registered()
    {
        _gw.AddQueueSubscription("tasks.process", "pool");

        _gw.HasQueueSubscription("tasks.process", "pool").ShouldBeTrue();
    }

    // Go: gateway.go — hasQueueSubscription returns false for unknown pair
    [Fact]
    public void HasQueueSubscription_false_when_not_registered()
    {
        _gw.HasQueueSubscription("unknown.subject", "nonexistent-group").ShouldBeFalse();
    }

    // Go: gateway.go — multiple queue groups can be registered for the same subject
    [Fact]
    public void Multiple_groups_per_subject()
    {
        _gw.AddQueueSubscription("jobs.run", "fast");
        _gw.AddQueueSubscription("jobs.run", "slow");
        _gw.AddQueueSubscription("jobs.run", "batch");

        var groups = _gw.GetQueueGroups("jobs.run");

        groups.Count.ShouldBe(3);
        groups.ShouldContain("fast");
        groups.ShouldContain("slow");
        groups.ShouldContain("batch");
    }

    // Go: gateway.go — subscriptions for different subjects are tracked independently
    [Fact]
    public void Different_subjects_tracked_independently()
    {
        _gw.AddQueueSubscription("subject.a", "group1");
        _gw.AddQueueSubscription("subject.b", "group2");

        _gw.HasQueueSubscription("subject.a", "group2").ShouldBeFalse();
        _gw.HasQueueSubscription("subject.b", "group1").ShouldBeFalse();
        _gw.HasQueueSubscription("subject.a", "group1").ShouldBeTrue();
        _gw.HasQueueSubscription("subject.b", "group2").ShouldBeTrue();
    }

    // Go: gateway.go — QueueSubscriptionCount reflects number of distinct subjects with queue groups
    [Fact]
    public void QueueSubscriptionCount_tracks_subjects()
    {
        _gw.QueueSubscriptionCount.ShouldBe(0);

        _gw.AddQueueSubscription("foo", "g1");
        _gw.QueueSubscriptionCount.ShouldBe(1);

        _gw.AddQueueSubscription("bar", "g2");
        _gw.QueueSubscriptionCount.ShouldBe(2);

        // Adding a second group to an existing subject does not increase count
        _gw.AddQueueSubscription("foo", "g3");
        _gw.QueueSubscriptionCount.ShouldBe(2);
    }

    // Go: gateway.go — removing a queue group that was never added is a no-op
    [Fact]
    public void RemoveQueueSubscription_no_error_for_unknown()
    {
        // Should not throw even though neither subject nor group was registered
        var act = () => _gw.RemoveQueueSubscription("never.registered", "ghost");
        act.ShouldNotThrow();
    }

    // Go: gateway.go — GetQueueGroups returns empty set for unknown subject
    [Fact]
    public void GetQueueGroups_empty_for_unknown_subject()
    {
        var groups = _gw.GetQueueGroups("nonexistent.subject");

        groups.ShouldNotBeNull();
        groups.Count.ShouldBe(0);
    }
}
