// Go parity: golang/nats-server/server/jetstream_cluster.go
// Covers: RaftGroup quorum calculation, HasQuorum checks, StreamAssignment
// and ConsumerAssignment creation, consumer dictionary operations,
// Preferred peer tracking.
using NATS.Server.JetStream.Cluster;

namespace NATS.Server.JetStream.Tests.JetStream.Cluster;

/// <summary>
/// Tests for ClusterAssignmentTypes: RaftGroup quorum semantics,
/// StreamAssignment lifecycle, and ConsumerAssignment defaults.
/// Go reference: jetstream_cluster.go:154-266 (raftGroup, streamAssignment, consumerAssignment).
/// </summary>
public class AssignmentSerializationTests
{
    // ---------------------------------------------------------------
    // RaftGroup quorum calculation
    // Go reference: jetstream_cluster.go:154-163 raftGroup.quorumNeeded()
    // ---------------------------------------------------------------

    [Fact]
    public void RaftGroup_quorum_size_for_single_node_is_one()
    {
        var group = new RaftGroup { Name = "test-r1", Peers = ["peer-1"] };

        group.QuorumSize.ShouldBe(1);
    }

    [Fact]
    public void RaftGroup_quorum_size_for_three_nodes_is_two()
    {
        var group = new RaftGroup { Name = "test-r3", Peers = ["p1", "p2", "p3"] };

        group.QuorumSize.ShouldBe(2);
    }

    [Fact]
    public void RaftGroup_quorum_size_for_five_nodes_is_three()
    {
        var group = new RaftGroup { Name = "test-r5", Peers = ["p1", "p2", "p3", "p4", "p5"] };

        group.QuorumSize.ShouldBe(3);
    }

    [Fact]
    public void RaftGroup_quorum_size_for_empty_peers_is_one()
    {
        var group = new RaftGroup { Name = "test-empty", Peers = [] };

        // (0 / 2) + 1 = 1
        group.QuorumSize.ShouldBe(1);
    }

    // ---------------------------------------------------------------
    // HasQuorum checks
    // Go reference: jetstream_cluster.go raftGroup quorum check
    // ---------------------------------------------------------------

    [Fact]
    public void HasQuorum_returns_true_when_acks_meet_quorum()
    {
        var group = new RaftGroup { Name = "q-test", Peers = ["p1", "p2", "p3"] };

        group.HasQuorum(2).ShouldBeTrue();
        group.HasQuorum(3).ShouldBeTrue();
    }

    [Fact]
    public void HasQuorum_returns_false_when_acks_below_quorum()
    {
        var group = new RaftGroup { Name = "q-test", Peers = ["p1", "p2", "p3"] };

        group.HasQuorum(1).ShouldBeFalse();
        group.HasQuorum(0).ShouldBeFalse();
    }

    [Fact]
    public void HasQuorum_single_node_requires_one_ack()
    {
        var group = new RaftGroup { Name = "q-r1", Peers = ["p1"] };

        group.HasQuorum(1).ShouldBeTrue();
        group.HasQuorum(0).ShouldBeFalse();
    }

    [Fact]
    public void HasQuorum_five_nodes_requires_three_acks()
    {
        var group = new RaftGroup { Name = "q-r5", Peers = ["p1", "p2", "p3", "p4", "p5"] };

        group.HasQuorum(2).ShouldBeFalse();
        group.HasQuorum(3).ShouldBeTrue();
        group.HasQuorum(5).ShouldBeTrue();
    }

    // ---------------------------------------------------------------
    // RaftGroup property defaults
    // Go reference: jetstream_cluster.go:154-163
    // ---------------------------------------------------------------

    [Fact]
    public void RaftGroup_defaults_storage_to_file()
    {
        var group = new RaftGroup { Name = "defaults" };

        group.StorageType.ShouldBe("file");
    }

    [Fact]
    public void RaftGroup_defaults_cluster_to_empty()
    {
        var group = new RaftGroup { Name = "defaults" };

        group.Cluster.ShouldBe(string.Empty);
    }

    [Fact]
    public void RaftGroup_preferred_peer_tracking()
    {
        var group = new RaftGroup { Name = "pref-test", Peers = ["p1", "p2", "p3"] };

        group.Preferred.ShouldBe(string.Empty);

        group.Preferred = "p2";
        group.Preferred.ShouldBe("p2");
    }

    // ---------------------------------------------------------------
    // StreamAssignment creation
    // Go reference: jetstream_cluster.go:166-184 streamAssignment
    // ---------------------------------------------------------------

    [Fact]
    public void StreamAssignment_created_with_defaults()
    {
        var group = new RaftGroup { Name = "sa-group", Peers = ["p1"] };
        var sa = new StreamAssignment
        {
            StreamName = "TEST-STREAM",
            Group = group,
        };

        sa.StreamName.ShouldBe("TEST-STREAM");
        sa.Group.ShouldBeSameAs(group);
        sa.ConfigJson.ShouldBe("{}");
        sa.SyncSubject.ShouldBe(string.Empty);
        sa.Responded.ShouldBeFalse();
        sa.Recovering.ShouldBeFalse();
        sa.Reassigning.ShouldBeFalse();
        sa.Consumers.ShouldBeEmpty();
        sa.Created.ShouldBeGreaterThan(DateTime.MinValue);
    }

    [Fact]
    public void StreamAssignment_consumers_dictionary_operations()
    {
        var group = new RaftGroup { Name = "sa-cons", Peers = ["p1", "p2", "p3"] };
        var sa = new StreamAssignment
        {
            StreamName = "MY-STREAM",
            Group = group,
        };

        var consumerGroup = new RaftGroup { Name = "cons-group", Peers = ["p1"] };
        var ca = new ConsumerAssignment
        {
            ConsumerName = "durable-1",
            StreamName = "MY-STREAM",
            Group = consumerGroup,
        };

        sa.Consumers["durable-1"] = ca;
        sa.Consumers.Count.ShouldBe(1);
        sa.Consumers["durable-1"].ConsumerName.ShouldBe("durable-1");

        sa.Consumers.Remove("durable-1");
        sa.Consumers.ShouldBeEmpty();
    }

    // ---------------------------------------------------------------
    // ConsumerAssignment creation
    // Go reference: jetstream_cluster.go:250-266 consumerAssignment
    // ---------------------------------------------------------------

    [Fact]
    public void ConsumerAssignment_created_with_defaults()
    {
        var group = new RaftGroup { Name = "ca-group", Peers = ["p1"] };
        var ca = new ConsumerAssignment
        {
            ConsumerName = "my-consumer",
            StreamName = "MY-STREAM",
            Group = group,
        };

        ca.ConsumerName.ShouldBe("my-consumer");
        ca.StreamName.ShouldBe("MY-STREAM");
        ca.Group.ShouldBeSameAs(group);
        ca.ConfigJson.ShouldBe("{}");
        ca.Responded.ShouldBeFalse();
        ca.Recovering.ShouldBeFalse();
        ca.Created.ShouldBeGreaterThan(DateTime.MinValue);
    }

    [Fact]
    public void ConsumerAssignment_mutable_flags()
    {
        var group = new RaftGroup { Name = "ca-flags", Peers = ["p1"] };
        var ca = new ConsumerAssignment
        {
            ConsumerName = "c1",
            StreamName = "S1",
            Group = group,
        };

        ca.Responded = true;
        ca.Recovering = true;

        ca.Responded.ShouldBeTrue();
        ca.Recovering.ShouldBeTrue();
    }

    [Fact]
    public void StreamAssignment_mutable_flags()
    {
        var group = new RaftGroup { Name = "sa-flags", Peers = ["p1"] };
        var sa = new StreamAssignment
        {
            StreamName = "S1",
            Group = group,
        };

        sa.Responded = true;
        sa.Recovering = true;
        sa.Reassigning = true;
        sa.ConfigJson = """{"subjects":["test.>"]}""";
        sa.SyncSubject = "$JS.SYNC.S1";

        sa.Responded.ShouldBeTrue();
        sa.Recovering.ShouldBeTrue();
        sa.Reassigning.ShouldBeTrue();
        sa.ConfigJson.ShouldBe("""{"subjects":["test.>"]}""");
        sa.SyncSubject.ShouldBe("$JS.SYNC.S1");
    }
}
