// Copyright 2012-2026 The NATS Authors
// Licensed under the Apache License, Version 2.0

using Shouldly;

namespace ZB.MOM.NatsNet.Server.Tests.JetStream;

public sealed class RaftNodeCoreTests
{
    [Fact]
    public void SnapshotHelpers_WhenEncodedAndLoaded_ShouldRoundTrip()
    {
        var storeDir = Path.Combine(Path.GetTempPath(), $"raft-node-core-{Guid.NewGuid():N}");
        var raft = new Raft
        {
            StoreDir = storeDir,
            Term_ = 3,
            Applied_ = 9,
            PApplied = 7,
            Wps = [1, 2, 3],
        };

        try
        {
            var checkpoint = raft.CreateSnapshotCheckpointLocked(force: true);
            checkpoint.ShouldNotBeNull();

            var snapshot = new Snapshot
            {
                LastTerm = 3,
                LastIndex = 9,
                PeerState = [7, 8],
                Data = [9, 10, 11],
            };

            raft.InstallSnapshotInternal(snapshot).ShouldBeNull();
            var (loaded, error) = raft.LoadLastSnapshot();
            error.ShouldBeNull();
            loaded.ShouldNotBeNull();
            loaded!.LastTerm.ShouldBe(3UL);
            loaded.LastIndex.ShouldBe(9UL);
            loaded.PeerState.ShouldBe([7, 8]);
            loaded.Data.ShouldBe([9, 10, 11]);

            raft.SetupLastSnapshot().ShouldBeNull();
            raft.PIndex.ShouldBe(9UL);
            raft.PTerm.ShouldBe(3UL);
        }
        finally
        {
            if (Directory.Exists(storeDir))
            {
                Directory.Delete(storeDir, recursive: true);
            }
        }
    }

    [Fact]
    public void LeadershipHelpers_WhenSteppingDownAndSelectingLeader_ShouldUpdateState()
    {
        var raft = new Raft
        {
            Id = "N1",
            StateValue = (int)RaftState.Candidate,
            Peers_ = new Dictionary<string, Lps>
            {
                ["N2"] = new() { Li = 3 },
                ["N3"] = new() { Li = 7 },
            },
        };

        raft.StepdownLocked("N3");
        raft.State().ShouldBe(RaftState.Follower);
        raft.LeaderId.ShouldBe("N3");
        raft.SelectNextLeader().ShouldBe("N3");
    }

    [Fact]
    public void CampaignHelpers_WhenLeaderOrFollower_ShouldReturnExpectedOutcome()
    {
        var raft = new Raft
        {
            StateValue = (int)RaftState.Leader,
        };

        raft.CampaignInternal(TimeSpan.FromMilliseconds(200)).ShouldNotBeNull();
        raft.XferCampaign().ShouldNotBeNull();

        raft.StateValue = (int)RaftState.Follower;
        raft.CampaignInternal(TimeSpan.FromMilliseconds(200)).ShouldBeNull();
        raft.State().ShouldBe(RaftState.Candidate);
    }

    [Fact]
    public void ProgressHelpers_WhenCatchupAndKnownPeersChange_ShouldTrackFlags()
    {
        var raft = new Raft
        {
            Commit = 10,
            Applied_ = 8,
            Catchup = new CatchupState(),
        };

        raft.IsCatchingUp().ShouldBeTrue();
        raft.IsCurrent(includeForwardProgress: true).ShouldBeFalse();
        raft.Catchup = null;
        raft.UpdateKnownPeersLocked(["N2", "N3"]);
        raft.Peers_.Count.ShouldBe(2);
        raft.RandCampaignTimeout().ShouldBeGreaterThan(TimeSpan.Zero);
    }

    [Fact]
    public void RunLoopHelpers_WhenInvoked_ShouldManageSubscriptionAndTimers()
    {
        var raft = new Raft
        {
            Id = "N1",
            GroupName = "RG",
            Active = DateTime.UtcNow,
        };

        var inbox = raft.NewInbox();
        var catchupInbox = raft.NewCatchupInbox();
        inbox.ShouldContain("_INBOX.");
        catchupInbox.ShouldContain("_INBOX.CATCHUP.");

        var sub = raft.Subscribe("raft.append");
        raft.AeSub.ShouldNotBeNull();
        raft.Unsubscribe(sub);
        raft.AeSub.ShouldBeNull();
        raft.CreateInternalSubs().ShouldBeNull();

        raft.ResetElectionTimeout();
        raft.Elect.ShouldNotBeNull();
        raft.ResetElect(TimeSpan.FromMilliseconds(10));
        raft.ElectTimer().ShouldBeGreaterThan(DateTime.MinValue);
    }

    [Fact]
    public void CodecHelpers_WhenRoundTrippingEntriesAndVotes_ShouldPreserveFields()
    {
        var raft = new Raft
        {
            GroupName = "RG",
        };

        var entry = raft.NewEntry(EntryType.EntryNormal, [1, 2, 3]);
        var proposed = raft.NewProposedEntry(entry, "reply");
        proposed.Reply.ShouldBe("reply");

        var appendEntry = raft.NewAppendEntry("L1", 2, 1, 1, 0, [entry]);
        appendEntry.String().ShouldContain("leader=L1");
        appendEntry.ShouldStore().ShouldBeTrue();
        var decodedAppend = raft.DecodeAppendEntry(appendEntry.Encode());
        decodedAppend.Leader.ShouldBe("L1");

        var appendResponse = raft.NewAppendEntryResponse(2, 1, "N2", "_R_", success: true);
        var decodedResponse = raft.DecodeAppendEntryResponse(appendResponse.Encode());
        decodedResponse.Success.ShouldBeTrue();
        decodedResponse.Peer.ShouldBe("N2");

        var voteRequest = new VoteRequest { TermV = 4, Candidate = "N3", LastIndex = 9, LastTerm = 3, Reply = "_R_" };
        var decodedVoteRequest = raft.DecodeVoteRequest(voteRequest.Encode());
        decodedVoteRequest.Candidate.ShouldBe("N3");

        var voteResponse = new VoteResponse { TermV = 4, Peer = "N2", Granted = true, Empty = false };
        var decodedVoteResponse = raft.DecodeVoteResponse(voteResponse.Encode());
        decodedVoteResponse.Granted.ShouldBeTrue();
        decodedVoteResponse.Peer.ShouldBe("N2");
    }

    [Fact]
    public void PeerStatePersistence_WhenWrittenAndRead_ShouldRoundTrip()
    {
        var raft = new Raft();
        var storeDir = Path.Combine(Path.GetTempPath(), $"raft-peer-state-{Guid.NewGuid():N}");
        try
        {
            var state = new PeerState
            {
                KnownPeers = ["A1", "A2"],
                ClusterSize = 2,
                DomainExt = 7,
            };

            raft.PeerStateBufSize(state).ShouldBeGreaterThan(0);
            raft.WritePeerStateStatic(storeDir, state).ShouldBeNull();
            var (readState, readError) = raft.ReadPeerState(storeDir);
            readError.ShouldBeNull();
            readState.ShouldNotBeNull();
            readState!.KnownPeers.Count.ShouldBe(2);

            raft.WriteTermVoteStatic(storeDir, 6, "A1").ShouldBeNull();
            raft.Term_.ShouldBe(6UL);
            raft.Vote.ShouldBe("A1");
        }
        finally
        {
            if (Directory.Exists(storeDir))
            {
                Directory.Delete(storeDir, recursive: true);
            }
        }
    }
}
