private boolean updateFollowerLogInformation(
      FollowerLogInformation followerLogInformation, AppendEntriesReply appendEntriesReply) {
    boolean updated = followerLogInformation.setMatchIndex(appendEntriesReply.getLogLastIndex());
    updated =
        followerLogInformation.setNextIndex(appendEntriesReply.getLogLastIndex() + 1) || updated;

    if (updated && LOG.isDebugEnabled()) {
      LOG.debug(
          "{}: handleAppendEntriesReply - FollowerLogInformation for {} updated: matchIndex: {}, nextIndex: {}",
          logName(),
          followerLogInformation.getId(),
          followerLogInformation.getMatchIndex(),
          followerLogInformation.getNextIndex());
    }
    return updated;
  }
  @Override
  protected RaftActorBehavior handleAppendEntriesReply(
      ActorRef sender, AppendEntriesReply appendEntriesReply) {

    if (LOG.isTraceEnabled()) {
      LOG.trace("{}: handleAppendEntriesReply: {}", logName(), appendEntriesReply);
    }

    // Update the FollowerLogInformation
    String followerId = appendEntriesReply.getFollowerId();
    FollowerLogInformation followerLogInformation = followerToLog.get(followerId);

    if (followerLogInformation == null) {
      LOG.error("{}: handleAppendEntriesReply - unknown follower {}", logName(), followerId);
      return this;
    }

    if (followerLogInformation.timeSinceLastActivity()
        > context.getConfigParams().getElectionTimeOutInterval().toMillis()) {
      LOG.warn(
          "{} : handleAppendEntriesReply delayed beyond election timeout, "
              + "appendEntriesReply : {}, timeSinceLastActivity : {}, lastApplied : {}, commitIndex : {}",
          logName(),
          appendEntriesReply,
          followerLogInformation.timeSinceLastActivity(),
          context.getLastApplied(),
          context.getCommitIndex());
    }

    followerLogInformation.markFollowerActive();
    followerLogInformation.setPayloadVersion(appendEntriesReply.getPayloadVersion());

    boolean updated = false;
    if (appendEntriesReply.isSuccess()) {
      updated = updateFollowerLogInformation(followerLogInformation, appendEntriesReply);
    } else {
      LOG.debug(
          "{}: handleAppendEntriesReply: received unsuccessful reply: {}",
          logName(),
          appendEntriesReply);

      long followerLastLogIndex = appendEntriesReply.getLogLastIndex();
      ReplicatedLogEntry followersLastLogEntry =
          context.getReplicatedLog().get(followerLastLogIndex);
      if (appendEntriesReply.isForceInstallSnapshot()) {
        // Reset the followers match and next index. This is to signal that this follower has
        // nothing
        // in common with this Leader and so would require a snapshot to be installed
        followerLogInformation.setMatchIndex(-1);
        followerLogInformation.setNextIndex(-1);

        // Force initiate a snapshot capture
        initiateCaptureSnapshot(followerId);
      } else if (followerLastLogIndex < 0
          || (followersLastLogEntry != null
              && followersLastLogEntry.getTerm() == appendEntriesReply.getLogLastTerm())) {
        // The follower's log is empty or the last entry is present in the leader's journal
        // and the terms match so the follower is just behind the leader's journal from
        // the last snapshot, if any. We'll catch up the follower quickly by starting at the
        // follower's last log index.

        updated = updateFollowerLogInformation(followerLogInformation, appendEntriesReply);
      } else {
        // TODO: When we find that the follower is out of sync with the
        // Leader we simply decrement that followers next index by 1.
        // Would it be possible to do better than this? The RAFT spec
        // does not explicitly deal with it but may be something for us to
        // think about.

        followerLogInformation.decrNextIndex();
      }
    }

    // Now figure out if this reply warrants a change in the commitIndex
    // If there exists an N such that N > commitIndex, a majority
    // of matchIndex[i] ≥ N, and log[N].term == currentTerm:
    // set commitIndex = N (§5.3, §5.4).
    for (long N = context.getCommitIndex() + 1; ; N++) {
      int replicatedCount = 1;

      for (FollowerLogInformation info : followerToLog.values()) {
        if (info.getMatchIndex() >= N) {
          replicatedCount++;
        }
      }

      if (replicatedCount >= minReplicationCount) {
        ReplicatedLogEntry replicatedLogEntry = context.getReplicatedLog().get(N);
        if (replicatedLogEntry != null && replicatedLogEntry.getTerm() == currentTerm()) {
          context.setCommitIndex(N);
        } else {
          break;
        }
      } else {
        break;
      }
    }

    // Apply the change to the state machine
    if (context.getCommitIndex() > context.getLastApplied()) {
      if (LOG.isDebugEnabled()) {
        LOG.debug(
            "{}: handleAppendEntriesReply from {}: applying to log - commitIndex: {}, lastAppliedIndex: {}",
            logName(),
            followerId,
            context.getCommitIndex(),
            context.getLastApplied());
      }

      applyLogToStateMachine(context.getCommitIndex());
    }

    if (!context.getSnapshotManager().isCapturing()) {
      purgeInMemoryLog();
    }

    // Send the next log entry immediately, if possible, no need to wait for heartbeat to trigger
    // that event
    sendUpdatesToFollower(followerId, followerLogInformation, false, !updated);
    return this;
  }