/**
 * Tests that a collection drop can be rolled back.
 * @tags: [
 *   requires_replication,
 *   requires_wiredtiger,
 *   requires_mongobridge,
 * ]
 */
import {RollbackTest} from "jstests/replsets/libs/rollback_test.js";

// Returns list of collections in database.
// Assumes all collections fit in first batch of results.
function listCollections(database) {
    return assert.commandWorked(database.runCommand({listCollections: 1})).cursor.firstBatch;
}

// Operations that will be present on both nodes, before the common point.
const collName = 'test.t';
const renameTargetCollName = 'test.x';
const noOpsToRollbackCollName = 'test.k';
let CommonOps = (node) => {
    const coll = node.getCollection(collName);
    const mydb = coll.getDB();
    assert.commandWorked(mydb.createCollection(coll.getName()));
    assert.commandWorked(coll.createIndex({a: 1}));
    assert.commandWorked(coll.insert({_id: 0, a: 0}));

    // Replicate a drop.
    const replicatedDropCollName = 'w';
    const collToDrop = mydb.getCollection(replicatedDropCollName);
    assert.commandWorked(mydb.createCollection(collToDrop.getName()));
    assert(collToDrop.drop());

    // This collection will be dropped during a rename.
    const renameTargetColl = node.getCollection(renameTargetCollName);
    assert.commandWorked(mydb.createCollection(renameTargetColl.getName()));
    assert.commandWorked(renameTargetColl.createIndex({b: 1}));
    assert.commandWorked(renameTargetColl.insert({_id: 8, b: 8}));
    assert.commandWorked(renameTargetColl.insert({_id: 9, b: 9}));

    // This collection will be dropped without any CRUD ops to rollback.
    const noOpsToRollbackColl = node.getCollection(noOpsToRollbackCollName);
    assert.commandWorked(mydb.createCollection(noOpsToRollbackColl.getName()));
    assert.commandWorked(noOpsToRollbackColl.createIndex({c: 1}));
    assert.commandWorked(noOpsToRollbackColl.insert({_id: 20, c: 20}));
    assert.commandWorked(noOpsToRollbackColl.insert({_id: 21, c: 21}));
};

// Operations that will be performed on the rollback node past the common point.
let RollbackOps = (node) => {
    const coll = node.getCollection(collName);

    // Rollback algorithm may refer to dropped collection if it has to undo an insert.
    assert.commandWorked(coll.insert({_id: 1, a: 1}));

    const mydb = coll.getDB();
    const collectionsBeforeDrop = listCollections(mydb);
    assert(coll.drop());
    const collectionsAfterDrop = listCollections(mydb);
    assert.lt(collectionsAfterDrop.length,
              collectionsBeforeDrop.length,
              'listCollections did not report fewer collections in database ' + mydb.getName() +
                  ' after dropping collection ' + coll.getFullName() + '. Before: ' +
                  tojson(collectionsBeforeDrop) + '. After: ' + tojson(collectionsAfterDrop));
    assert.gt(mydb.serverStatus().storageEngine.dropPendingIdents,
              0,
              'There is no drop pending ident in the storage engine.');

    const renameTargetColl = node.getCollection(renameTargetCollName);
    assert.commandWorked(renameTargetColl.insert({_id: 10, b: 10}));
    assert.commandWorked(renameTargetColl.insert({_id: 11, b: 11}));
    const renameSourceColl = mydb.getCollection('z');
    assert.commandWorked(mydb.createCollection(renameSourceColl.getName()));
    assert.commandWorked(renameSourceColl.renameCollection(renameTargetColl.getName(), true));

    const noOpsToRollbackColl = node.getCollection(noOpsToRollbackCollName);
    assert(noOpsToRollbackColl.drop());

    // This collection will not exist after rollback.
    const tempColl = node.getCollection('test.a');
    assert.commandWorked(mydb.createCollection(tempColl.getName()));
    assert.commandWorked(tempColl.insert({_id: 100, y: 100}));
    assert(tempColl.drop());
};

// Set up Rollback Test.
const rollbackTest = new RollbackTest();
CommonOps(rollbackTest.getPrimary());

const rollbackNode = rollbackTest.transitionToRollbackOperations();
RollbackOps(rollbackNode);

{
    // Check collection drop oplog entry.
    const replTest = rollbackTest.getTestFixture();
    const ops = replTest.dumpOplog(rollbackNode, {ns: 'test.$cmd', 'o.drop': 't'});
    assert.eq(1, ops.length);
    const op = ops[0];
    assert(op.hasOwnProperty('o2'), 'expected o2 field in drop oplog entry: ' + tojson(op));
    assert(op.o2.hasOwnProperty('numRecords'), 'expected count in drop oplog entry: ' + tojson(op));
    assert.eq(2, op.o2.numRecords, 'incorrect count in drop oplog entry: ' + tojson(op));
}

// Check collection rename oplog entry.
{
    const replTest = rollbackTest.getTestFixture();
    const ops = replTest.dumpOplog(
        rollbackNode, {ns: 'test.$cmd', 'o.renameCollection': 'test.z', 'o.to': 'test.x'});
    assert.eq(1, ops.length);
    const op = ops[0];
    assert(op.hasOwnProperty('o2'), 'expected o2 field in rename oplog entry: ' + tojson(op));
    assert(op.o2.hasOwnProperty('numRecords'),
           'expected count in rename oplog entry: ' + tojson(op));
    assert.eq(4, op.o2.numRecords, 'incorrect count in rename oplog entry: ' + tojson(op));
}

// Wait for rollback to finish.
rollbackTest.transitionToSyncSourceOperationsBeforeRollback();
rollbackTest.transitionToSyncSourceOperationsDuringRollback();
rollbackTest.transitionToSteadyStateOperations();

// Check collection count.
const primary = rollbackTest.getPrimary();
const coll = primary.getCollection(collName);
assert.eq(1, coll.find().itcount());
assert.eq(1, coll.count());
const renameTargetColl = primary.getCollection(renameTargetCollName);
assert.eq(2, renameTargetColl.find().itcount());
assert.eq(2, renameTargetColl.count());
const noOpsToRollbackColl = primary.getCollection(noOpsToRollbackCollName);
assert.eq(2, noOpsToRollbackColl.find().itcount());
assert.eq(2, noOpsToRollbackColl.count());

rollbackTest.stop();
