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

using System.Threading.Channels;
using Shouldly;
using ZB.MOM.NatsNet.Server.Internal;

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

public sealed class RaftTypesTests
{
    [Fact]
    public void Raft_Methods_ShouldProvideNonStubBehavior()
    {
        var raft = new Raft
        {
            Id = "N1",
            GroupName = "RG",
            AccName = "ACC",
            Server_ = new object(),
            StateValue = (int)RaftState.Leader,
            LeaderId = "N1",
            Csz = 3,
            Qn = 2,
            PIndex = 10,
            Commit = 8,
            Applied_ = 6,
            Processed_ = 7,
            PApplied = 9,
            WalBytes = 128,
            Peers_ = new Dictionary<string, Lps>
            {
                ["N2"] = new() { Ts = DateTime.UtcNow, Kp = true, Li = 9 },
            },
            ApplyQ_ = new IpQueue<CommittedEntry>("apply-q"),
            LeadC = Channel.CreateUnbounded<bool>(),
            Quit = Channel.CreateUnbounded<bool>(),
        };

        raft.Propose([1, 2, 3]);
        raft.ForwardProposal([4, 5]);
        raft.ProposeMulti([new Entry { Data = [6] }]);

        raft.PropQ.ShouldNotBeNull();
        raft.PropQ!.Len().ShouldBe(3);

        raft.InstallSnapshot([9, 9], force: false);
        raft.SendSnapshot([8, 8, 8]);
        raft.CreateSnapshotCheckpoint(force: false).ShouldBeOfType<Checkpoint>();
        raft.NeedSnapshot().ShouldBeTrue();

        raft.Applied(5).ShouldBe((1UL, 128UL));
        raft.Processed(11, 10).ShouldBe((11UL, 128UL));
        raft.Size().ShouldBe((11UL, 128UL));
        raft.Progress().ShouldBe((10UL, 8UL, 10UL));
        raft.Leader().ShouldBeTrue();
        raft.LeaderSince().ShouldNotBeNull();
        raft.Quorum().ShouldBeTrue();
        raft.Current().ShouldBeTrue();
        raft.Healthy().ShouldBeTrue();
        raft.Term().ShouldBe(raft.Term_);
        raft.Leaderless().ShouldBeFalse();
        raft.GroupLeader().ShouldBe("N1");

        raft.SetObserver(true);
        raft.IsObserver().ShouldBeTrue();
        raft.Campaign();
        raft.State().ShouldBe(RaftState.Candidate);
        raft.CampaignImmediately();
        raft.StepDown("N2");
        raft.State().ShouldBe(RaftState.Follower);

        raft.ProposeKnownPeers(["P1", "P2"]);
        raft.Peers().Count.ShouldBe(3);
        raft.ProposeAddPeer("P3");
        raft.ClusterSize().ShouldBeGreaterThan(1);
        raft.ProposeRemovePeer("P2");
        raft.Peers().Count.ShouldBe(3);
        raft.MembershipChangeInProgress().ShouldBeTrue();
        raft.AdjustClusterSize(5);
        raft.ClusterSize().ShouldBe(5);
        raft.AdjustBootClusterSize(4);
        raft.ClusterSize().ShouldBe(4);

        raft.ApplyQ().ShouldNotBeNull();
        raft.PauseApply();
        raft.Paused.ShouldBeTrue();
        raft.ResumeApply();
        raft.Paused.ShouldBeFalse();
        raft.DrainAndReplaySnapshot().ShouldBeTrue();
        raft.LeadChangeC().ShouldNotBeNull();
        raft.QuitC().ShouldNotBeNull();
        raft.Created().ShouldBe(raft.Created_);
        raft.ID().ShouldBe("N1");
        raft.Group().ShouldBe("RG");
        raft.GetTrafficAccountName().ShouldBe("ACC");

        raft.RecreateInternalSubs();
        raft.Stop();
        raft.WaitForStop();
        raft.Delete();
        raft.IsDeleted().ShouldBeTrue();
    }

    [Fact]
    public void Checkpoint_Methods_ShouldRoundTripSnapshotData()
    {
        var node = new Raft
        {
            Id = "NODE",
            PTerm = 3,
            AReply = "_R_",
        };

        var checkpoint = new Checkpoint
        {
            Node = node,
            Term = 5,
            Applied = 11,
            PApplied = 7,
            SnapFile = Path.Combine(Path.GetTempPath(), $"checkpoint-{Guid.NewGuid():N}.bin"),
        };

        var written = checkpoint.InstallSnapshot([1, 2, 3, 4]);
        written.ShouldBe(4UL);

        var loaded = checkpoint.LoadLastSnapshot();
        loaded.ShouldBe([1, 2, 3, 4]);

        var seq = checkpoint.AppendEntriesSeq().ToList();
        seq.Count.ShouldBe(1);
        seq[0].Error.ShouldBeNull();
        seq[0].Entry.Leader.ShouldBe("NODE");
        seq[0].Entry.TermV.ShouldBe(5UL);
        seq[0].Entry.Commit.ShouldBe(11UL);
        seq[0].Entry.PIndex.ShouldBe(7UL);

        checkpoint.Abort();
        File.Exists(checkpoint.SnapFile).ShouldBeFalse();
    }

    [Fact]
    public void EntryAndPoolHelpers_ShouldReturnExpectedRepresentations()
    {
        EntryType.EntryAddPeer.String().ShouldBe("EntryAddPeer");

        var committed = new CommittedEntry { Index = 2, Entries = [new Entry { Type = EntryType.EntryNormal, Data = [1] }] };
        committed.ReturnToPool();
        committed.Index.ShouldBe(0UL);
        committed.Entries.ShouldBeEmpty();

        var proposed = new ProposedEntry { Entry = new Entry { Type = EntryType.EntryNormal, Data = [2] }, Reply = "_R_" };
        proposed.ReturnToPool();
        proposed.Entry.ShouldBeNull();
        proposed.Reply.ShouldBeEmpty();

        var appendEntry = new AppendEntry
        {
            Leader = "N1",
            TermV = 2,
            Commit = 1,
            PTerm = 1,
            PIndex = 0,
            Entries = [new Entry { Type = EntryType.EntryNormal, Data = [3] }],
            Reply = "_R_",
        };

        appendEntry.ReturnToPool();
        appendEntry.Leader.ShouldBeEmpty();
        appendEntry.Entries.ShouldBeEmpty();
    }

    [Fact]
    public void CatchupAndCommitMethods_ShouldTrackProgress()
    {
        var raft = new Raft
        {
            Id = "N1",
            GroupName = "RG",
            StateValue = (int)RaftState.Leader,
            Term_ = 4,
            PIndex = 10,
            Commit = 10,
            Peers_ = new Dictionary<string, Lps>(),
        };

        var entry = raft.NewAppendEntry("N1", 4, 11, 4, 10, [raft.NewEntry(EntryType.EntryNormal, [1, 2])]);
        raft.StoreToWAL(entry).ShouldBeTrue();
        raft.LoadEntry(11).ShouldNotBeNull();
        raft.TrackPeer("N2", 11);
        raft.CatchupFollower("N2", 4, 11).Signal.ShouldBeTrue();
        raft.SendCatchupSignal().ShouldBeTrue();
        raft.CancelCatchupSignal();
        raft.Catchup!.Signal.ShouldBeFalse();
        raft.TryCommit(11).ShouldBeTrue();
        raft.ApplyQ_.ShouldNotBeNull();
        raft.ApplyQ_!.Len().ShouldBeGreaterThan(0);
        raft.UpdateLeader("N2");
        raft.GroupLeader().ShouldBe("N2");
        raft.TruncateWAL(10);
        raft.LoadEntry(11).ShouldBeNull();
        raft.ResetWAL();
        raft.PIndex.ShouldBe(0UL);
    }

    [Fact]
    public void AppendProcessingMethods_ShouldHandlePeerStateAndHeartbeat()
    {
        var raft = new Raft
        {
            Id = "N1",
            GroupName = "RG",
            StoreDir = Path.Combine(Path.GetTempPath(), $"raft-{Guid.NewGuid():N}"),
            Qn = 2,
            Csz = 3,
            PIndex = 5,
            PTerm = 2,
            Commit = 4,
            Peers_ = new Dictionary<string, Lps>
            {
                ["N2"] = new() { Kp = true, Li = 5, Ts = DateTime.UtcNow },
            },
        };

        var appendEntry = raft.BuildAppendEntry("N2");
        appendEntry.Leader.ShouldBe("N1");
        raft.SendAppendEntry(appendEntry, "N2");
        raft.SendQ.ShouldNotBeNull();

        var peerStateEntry = raft.NewEntry(EntryType.EntryPeerState, raft.EncodePeerState(new PeerState
        {
            KnownPeers = ["N2", "N3"],
            ClusterSize = 3,
        }));
        raft.ProcessPeerState(peerStateEntry);
        raft.PeerNames().ShouldContain("N2");
        raft.PeerNames().ShouldContain("N3");
        raft.CurrentPeerState().ClusterSize.ShouldBe(3);
        raft.SendPeerState();
        raft.SendHeartbeat();
        raft.WritePeerState().ShouldBeNull();
        raft.WriteTermVoteStatic(raft.StoreDir, 7, "N2").ShouldBeNull();
        var termVote = raft.ReadTermVote();
        termVote.Error.ShouldBeNull();
        termVote.Term.ShouldBe(7UL);
        termVote.Vote.ShouldBe("N2");
    }

    [Fact]
    public void ElectionMethods_ShouldTransitionStatesBasedOnVotes()
    {
        var raft = new Raft
        {
            Id = "N1",
            GroupName = "RG",
            StoreDir = Path.Combine(Path.GetTempPath(), $"raft-{Guid.NewGuid():N}"),
            StateValue = (int)RaftState.Follower,
            Csz = 3,
            Qn = 2,
            PIndex = 9,
            PTerm = 3,
            VSubj = "raft.vote",
            VReply = "raft.vote.reply",
        };

        raft.SwitchToCandidate();
        raft.State().ShouldBe(RaftState.Candidate);
        raft.QuorumNeeded().ShouldBe(2);
        raft.RequestVote();
        raft.SendQ.ShouldNotBeNull();
        raft.ProcessVoteRequest(new VoteRequest
        {
            TermV = raft.Term_,
            LastTerm = raft.PTerm,
            LastIndex = raft.PIndex,
            Candidate = "N2",
            Reply = "reply-subject",
        }).ShouldBeFalse();
        raft.HandleVoteResponse(new VoteResponse { TermV = raft.Term_, Peer = "N2", Granted = true });
        raft.State().ShouldBe(RaftState.Leader);
        raft.State().ShouldBe(RaftState.Leader);
        raft.IsClosed().ShouldBeFalse();
        raft.SetWriteErr(new InvalidOperationException("write"));
        raft.WriteErr.ShouldNotBeNull();
        raft.SwitchToFollower("N2");
        raft.State().ShouldBe(RaftState.Follower);
        raft.GroupLeader().ShouldBe("N2");
    }
}
