using NATS.Server.JetStream.Cluster;

namespace NATS.Server.Tests.JetStream.Cluster;

/// <summary>
/// Tests for validated stream/consumer assignment processing.
/// Go reference: jetstream_cluster.go:4541-5925.
/// </summary>
public class JetStreamAssignmentProcessingTests
{
    [Fact]
    public void ProcessStreamAssignment_validates_config()
    {
        var meta = new JetStreamMetaGroup(3);
        var sa = new StreamAssignment
        {
            StreamName = "valid-stream",
            Group = new RaftGroup { Name = "rg-1", Peers = ["n1", "n2", "n3"] },
            ConfigJson = """{"subjects":["test.>"]}""",
        };

        meta.ProcessStreamAssignment(sa).ShouldBeTrue();
        meta.StreamCount.ShouldBe(1);
    }

    [Fact]
    public void ProcessStreamAssignment_rejects_empty_name()
    {
        var meta = new JetStreamMetaGroup(3);
        var sa = CreateStreamAssignment("", "rg-1");
        meta.ProcessStreamAssignment(sa).ShouldBeFalse();
        meta.StreamCount.ShouldBe(0);
    }

    [Fact]
    public void ProcessUpdateStreamAssignment_applies_config_change()
    {
        var meta = new JetStreamMetaGroup(3);
        meta.ProcessStreamAssignment(CreateStreamAssignment("updatable", "rg-u", """{"subjects":["old.>"]}"""));

        var updated = CreateStreamAssignment("updatable", "rg-u", """{"subjects":["new.>"]}""");
        meta.ProcessUpdateStreamAssignment(updated).ShouldBeTrue();

        var assignment = meta.GetStreamAssignment("updatable");
        assignment!.ConfigJson.ShouldContain("new.>");
    }

    [Fact]
    public void ProcessUpdateStreamAssignment_returns_false_for_nonexistent()
    {
        var meta = new JetStreamMetaGroup(3);
        var sa = CreateStreamAssignment("ghost", "rg-g");
        meta.ProcessUpdateStreamAssignment(sa).ShouldBeFalse();
    }

    [Fact]
    public void ProcessConsumerAssignment_requires_existing_stream()
    {
        var meta = new JetStreamMetaGroup(3);
        var ca = new ConsumerAssignment
        {
            ConsumerName = "orphan-consumer",
            StreamName = "nonexistent-stream",
            Group = new RaftGroup { Name = "rg-c", Peers = ["n1", "n2", "n3"] },
        };

        meta.ProcessConsumerAssignment(ca).ShouldBeFalse();
    }

    [Fact]
    public void ProcessConsumerAssignment_succeeds_with_existing_stream()
    {
        var meta = new JetStreamMetaGroup(3);
        meta.ProcessStreamAssignment(CreateStreamAssignment("s1", "rg-s1"));

        var ca = new ConsumerAssignment
        {
            ConsumerName = "c1",
            StreamName = "s1",
            Group = new RaftGroup { Name = "rg-c1", Peers = ["n1", "n2", "n3"] },
        };

        meta.ProcessConsumerAssignment(ca).ShouldBeTrue();
        meta.ConsumerCount.ShouldBe(1);
    }

    [Fact]
    public void ProcessStreamRemoval_cascades_to_consumers()
    {
        var meta = new JetStreamMetaGroup(3);
        meta.ProcessStreamAssignment(CreateStreamAssignment("cascade", "rg-cas"));
        meta.ProcessConsumerAssignment(new ConsumerAssignment
        {
            ConsumerName = "c1",
            StreamName = "cascade",
            Group = new RaftGroup { Name = "rg-c1", Peers = ["n1", "n2", "n3"] },
        });

        meta.ProcessStreamRemoval("cascade").ShouldBeTrue();
        meta.StreamCount.ShouldBe(0);
        meta.ConsumerCount.ShouldBe(0);
    }

    [Fact]
    public void ProcessStreamRemoval_returns_false_for_nonexistent()
    {
        var meta = new JetStreamMetaGroup(3);
        meta.ProcessStreamRemoval("nope").ShouldBeFalse();
    }

    [Fact]
    public void ProcessConsumerRemoval_returns_false_for_nonexistent_stream()
    {
        var meta = new JetStreamMetaGroup(3);
        meta.ProcessConsumerRemoval("ghost", "c1").ShouldBeFalse();
    }

    [Fact]
    public void ProcessConsumerRemoval_returns_false_for_nonexistent_consumer()
    {
        var meta = new JetStreamMetaGroup(3);
        meta.ProcessStreamAssignment(CreateStreamAssignment("s1", "rg-s1"));
        meta.ProcessConsumerRemoval("s1", "nope").ShouldBeFalse();
    }

    [Fact]
    public void ProcessConsumerRemoval_succeeds()
    {
        var meta = new JetStreamMetaGroup(3);
        meta.ProcessStreamAssignment(CreateStreamAssignment("s1", "rg-s1"));
        meta.ProcessConsumerAssignment(new ConsumerAssignment
        {
            ConsumerName = "c1",
            StreamName = "s1",
            Group = new RaftGroup { Name = "rg-c1", Peers = ["n1", "n2"] },
        });

        meta.ProcessConsumerRemoval("s1", "c1").ShouldBeTrue();
        meta.ConsumerCount.ShouldBe(0);
    }

    [Fact]
    public void ProcessUpdateStreamAssignment_preserves_consumers()
    {
        var meta = new JetStreamMetaGroup(3);
        meta.ProcessStreamAssignment(CreateStreamAssignment("s1", "rg-s1", """{"subjects":["old"]}"""));
        meta.ProcessConsumerAssignment(new ConsumerAssignment
        {
            ConsumerName = "c1",
            StreamName = "s1",
            Group = new RaftGroup { Name = "rg-c1", Peers = ["n1", "n2"] },
        });

        var updated = CreateStreamAssignment("s1", "rg-s1", """{"subjects":["new"]}""");
        meta.ProcessUpdateStreamAssignment(updated).ShouldBeTrue();

        meta.ConsumerCount.ShouldBe(1);
        meta.GetConsumerAssignment("s1", "c1").ShouldNotBeNull();
    }

    // Helper to create a StreamAssignment (StreamName is `required` so we must always provide it)
    private static StreamAssignment CreateStreamAssignment(string name, string groupName, string config = "{}")
        => new()
        {
            StreamName = name,
            Group = new RaftGroup { Name = groupName, Peers = ["n1", "n2", "n3"] },
            ConfigJson = config,
        };
}
