/**
 * Tests that the recipient in a migration correctly tracks the number of orphans during cloning and
 * transfer mods. The count of orphan documents should be persisted in the range deletion document
 * in config.rangeDeletions.
 *
 * @tags: [
 *  requires_fcv_60,
 * ]
 */

import {configureFailPoint} from "jstests/libs/fail_point_util.js";
import {funWithArgs} from "jstests/libs/parallel_shell_helpers.js";
import {ShardingTest} from "jstests/libs/shardingtest.js";

const st = new ShardingTest({shards: 2});

// Setup database and collection for test
const dbName = 'db';
const db = st.getDB(dbName);
assert.commandWorked(
    st.s.adminCommand({enableSharding: dbName, primaryShard: st.shard0.shardName}));
const coll = db['test'];
const nss = coll.getFullName();
assert.commandWorked(st.s.adminCommand({shardCollection: nss, key: {_id: 1}}));

function assertOrphanCountIsCorrect(conn, ns, numOrphans) {
    const rangeDeletionDoc =
        conn.getDB("config").getCollection("rangeDeletions").findOne({nss: ns});
    assert.neq(null,
               rangeDeletionDoc,
               "did not find document for namespace " + ns +
                   ", contents of config.rangeDeletions on " + conn + ": " +
                   tojson(conn.getDB("config").getCollection("rangeDeletions").find().toArray()));
    assert.eq(numOrphans,
              rangeDeletionDoc.numOrphanDocs,
              "Incorrect count of orphaned documents in config.rangeDeletions on " + conn +
                  ": expected " + numOrphans +
                  " orphaned documents but found range deletion document " +
                  tojson(rangeDeletionDoc));
}

// Insert some docs into the collection.
const numDocs = 1000;
let bulk = coll.initializeUnorderedBulkOp();
for (let i = 0; i < numDocs; i++) {
    bulk.insert({_id: i});
}
assert.commandWorked(bulk.execute());

// Pause after bulk clone and check number of orphans is equal to numDocs
let bulkCloneFailpoint = configureFailPoint(st.shard1, "migrateThreadHangAtStep4");
const awaitResult = startParallelShell(
    funWithArgs(function(nss, toShardName) {
        assert.commandWorked(db.adminCommand({moveChunk: nss, find: {_id: 0}, to: toShardName}));
    }, nss, st.shard1.shardName), st.s.port);

// Assert that the range deletion document is present and has the correct number of orphans.
bulkCloneFailpoint.wait();
assertOrphanCountIsCorrect(st.shard1, nss, numDocs);
assertOrphanCountIsCorrect(st.shard0, nss, 0);

// Pause after transfer mods and check number of orphans has changed correctly.
let transferModsFailpoint = configureFailPoint(st.shard1, "migrateThreadHangAtStep5");

// Perform some upserts and deletes to change the number of orphans on the recipient.
let bulkMods = coll.initializeUnorderedBulkOp();
const numUpserts = 50;
for (let i = 0; i < numUpserts; i++) {
    let key = numDocs + i;
    bulkMods.find({_id: key}).upsert().update({$set: {_id: key}});
}
const numDeletes = 25;
for (let i = 0; i < numDeletes; i++) {
    bulkMods.find({_id: i}).removeOne();
}
// Perform some updates that shouldn't change the number of orphans.
const numUpdates = 10;
for (let i = 0; i < numUpdates; i++) {
    let key = numDeletes + i;
    bulkMods.find({_id: key}).update({$set: {x: key}});
}
assert.commandWorked(bulkMods.execute());

// Assert that the number of orphans is still correct.
bulkCloneFailpoint.off();
transferModsFailpoint.wait();
const updatedOrphanCount = numDocs + numUpserts - numDeletes;
assertOrphanCountIsCorrect(st.shard1, nss, updatedOrphanCount);
assertOrphanCountIsCorrect(st.shard0, nss, 0);

// Allow migration to finish but stop right after updating range deletion documents
let migrationCommittedFailpoint =
    configureFailPoint(st.shard0, "hangBeforeForgettingMigrationAfterCommitDecision");
transferModsFailpoint.off();
migrationCommittedFailpoint.wait();
assertOrphanCountIsCorrect(st.shard0, nss, updatedOrphanCount);
assert.eq(0, st.shard1.getDB("config").getCollection("rangeDeletions").find().itcount());

// Allow migration to fully complete
migrationCommittedFailpoint.off();
awaitResult();

st.stop();
