using NATS.Server.Raft;

namespace NATS.Server.Raft.Tests.Raft;

public class RaftNodeParityBatch2Tests
{
    private static RaftNode ElectSingleNodeLeader()
    {
        var node = new RaftNode("n1");
        node.ConfigureCluster([node]);
        node.StartElection(1);
        node.IsLeader.ShouldBeTrue();
        return node;
    }

    [Fact]
    public void Leader_tracking_flags_update_on_election_and_heartbeat()
    {
        var node1 = new RaftNode("n1");
        var node2 = new RaftNode("n2");
        var node3 = new RaftNode("n3");

        node1.ConfigureCluster([node1, node2, node3]);
        node2.ConfigureCluster([node1, node2, node3]);
        node3.ConfigureCluster([node1, node2, node3]);

        node1.StartElection(3);
        node1.ReceiveVote(node2.GrantVote(node1.Term, node1.Id), 3);

        node1.IsLeader.ShouldBeTrue();
        node1.GroupLeader.ShouldBe("n1");
        node1.Leaderless.ShouldBeFalse();
        node1.HadPreviousLeader.ShouldBeTrue();
        node1.LeaderSince.ShouldNotBeNull();

        node2.ReceiveHeartbeat(node1.Term, fromPeerId: "n1");
        node2.IsLeader.ShouldBeFalse();
        node2.GroupLeader.ShouldBe("n1");
        node2.Leaderless.ShouldBeFalse();
        node2.HadPreviousLeader.ShouldBeTrue();
        node2.LeaderSince.ShouldBeNull();
    }

    [Fact]
    public void Stepdown_clears_group_leader_and_leader_since()
    {
        using var leader = ElectSingleNodeLeader();
        leader.GroupLeader.ShouldBe("n1");
        leader.LeaderSince.ShouldNotBeNull();

        leader.RequestStepDown();

        leader.Leaderless.ShouldBeTrue();
        leader.GroupLeader.ShouldBe(RaftNode.NoLeader);
        leader.LeaderSince.ShouldBeNull();
    }

    [Fact]
    public void Observer_mode_can_be_toggled()
    {
        using var node = new RaftNode("n1");
        node.IsObserver.ShouldBeFalse();

        node.SetObserver(true);
        node.IsObserver.ShouldBeTrue();

        node.SetObserver(false);
        node.IsObserver.ShouldBeFalse();
    }

    [Fact]
    public void Cluster_size_adjustments_enforce_boot_and_leader_rules()
    {
        using var node = new RaftNode("n1");
        node.ClusterSize().ShouldBe(1);

        node.AdjustBootClusterSize(1).ShouldBeTrue();
        node.ClusterSize().ShouldBe(2); // floor is 2

        node.ConfigureCluster([node]);
        node.StartElection(1);
        node.IsLeader.ShouldBeTrue();

        node.AdjustClusterSize(5).ShouldBeTrue();
        node.ClusterSize().ShouldBe(5);
        node.AdjustBootClusterSize(7).ShouldBeFalse();
    }

    [Fact]
    public async Task Progress_size_and_applied_accessors_report_expected_values()
    {
        using var leader = ElectSingleNodeLeader();
        await leader.ProposeAsync("abc", CancellationToken.None);
        await leader.ProposeAsync("de", CancellationToken.None);

        var progress = leader.Progress();
        progress.Index.ShouldBe(2);
        progress.Commit.ShouldBe(2);
        progress.Applied.ShouldBe(2);

        var size = leader.Size();
        size.Entries.ShouldBe(2);
        size.Bytes.ShouldBe(5);

        var applied = leader.Applied(1);
        applied.Entries.ShouldBe(1);
        applied.Bytes.ShouldBe(3);
        leader.ProcessedIndex.ShouldBe(1);
    }

    [Fact]
    public void Campaign_timeout_randomization_and_defaults_match_go_constants()
    {
        using var node = new RaftNode("n1");
        for (var i = 0; i < 20; i++)
        {
            var timeout = node.RandomizedCampaignTimeout();
            timeout.ShouldBeGreaterThanOrEqualTo(RaftNode.MinCampaignTimeoutDefault);
            timeout.ShouldBeLessThan(RaftNode.MaxCampaignTimeoutDefault);
        }

        RaftNode.HbIntervalDefault.ShouldBe(TimeSpan.FromSeconds(1));
        RaftNode.LostQuorumIntervalDefault.ShouldBe(TimeSpan.FromSeconds(10));
        RaftNode.ObserverModeIntervalDefault.ShouldBe(TimeSpan.FromHours(48));
        RaftNode.PeerRemoveTimeoutDefault.ShouldBe(TimeSpan.FromMinutes(5));
        RaftNode.NoLeader.ShouldBe(string.Empty);
        RaftNode.NoVote.ShouldBe(string.Empty);
    }

    [Fact]
    public void Stop_wait_for_stop_and_delete_set_lifecycle_state()
    {
        var path = Path.Combine(Path.GetTempPath(), $"raft-node-delete-{Guid.NewGuid():N}");
        Directory.CreateDirectory(path);
        File.WriteAllText(Path.Combine(path, "marker.txt"), "x");

        using var node = new RaftNode("n1", persistDirectory: path);
        node.IsDeleted.ShouldBeFalse();

        node.Stop();
        node.WaitForStop();
        node.IsDeleted.ShouldBeFalse();
        Directory.Exists(path).ShouldBeTrue();

        node.Delete();
        node.IsDeleted.ShouldBeTrue();
        Directory.Exists(path).ShouldBeFalse();
    }
}
