/*
 * @tags: [
 *     # Uses failpoints that the mongos does not have.
 *     assumes_against_mongod_not_mongos,
 *     # Sets a failpoint on one mongod, so switching primaries would break the test.
 *     does_not_support_stepdowns,
 * ]
 */
import {funWithArgs} from "jstests/libs/parallel_shell_helpers.js";

const dbName = "test";
const collName = "create_index_conflicts";
const testDB = db.getSiblingDB(dbName);
testDB.dropDatabase();
const testColl = testDB.getCollection(collName);

// Test scenario where a WriteConflictException is caught during createIndexes.
assert.commandWorked(db.adminCommand(
    {configureFailPoint: "createIndexesWriteConflict", mode: {activationProbability: 0.5}}));

// Make sure at least one execution occurs where the failpoint activates.
for (let i = 0; i < 20; i++) {
    const res = assert.commandWorked(testColl.createIndex({a: 1}));
    assert.eq(1, res.numIndexesBefore);
    assert.eq(2, res.numIndexesAfter);
    assert(res.createdCollectionAutomatically);
    testColl.drop();
}
assert.commandWorked(
    db.adminCommand({configureFailPoint: "createIndexesWriteConflict", mode: "off"}));

// Test scenario where a conflicting collection creation occurs, and createIndexes "loses" to the
// create collection.
assert.commandWorked(testDB.adminCommand({clearLog: 'global'}));

function runSuccessfulIndexBuild(dbName, collName) {
    jsTest.log("Index build request starting...");
    const res = db.getSiblingDB(dbName).runCommand(
        {createIndexes: collName, indexes: [{key: {b: 1}, name: "the_b_1_index"}]});
    jsTest.log("Index build request expected to succeed, result: " + tojson(res));
    assert.commandWorked(res);
    // Since the createCollection succeeded first, the index build should proceed as if the
    // collection already existed.
    assert(!res.createdCollectionAutomatically);
}

// Simulate a scenario where a createCollection succeeds before createIndexes can create the same
// collection.
function runConflictingCollectionCreate(testDB, dbName, collName) {
    testColl.drop();
    assert.commandWorked(testDB.adminCommand(
        {configureFailPoint: 'hangBeforeCreateIndexesCollectionCreate', mode: 'alwaysOn'}));
    let joinCollectionCreate;
    let joinIndexBuild;
    try {
        jsTest.log("Starting a parallel shell to run index build request...");
        joinIndexBuild = startParallelShell(funWithArgs(runSuccessfulIndexBuild, dbName, collName),
                                            db.getMongo().port);
        jsTest.log("Waiting for create collection hang during index build...");
        checkLog.contains(
            db.getMongo(),
            "Hanging create collection due to failpoint 'hangBeforeCreateIndexesCollectionCreate'");

        jsTest.log("Create collection request starting...");
        const res = db.getSiblingDB(dbName).runCommand({create: collName});
        jsTest.log("Create collection request expected to succeed, result: " + tojson(res));
        assert.commandWorked(res);
    } finally {
        assert.commandWorked(testDB.adminCommand(
            {configureFailPoint: 'hangBeforeCreateIndexesCollectionCreate', mode: 'off'}));
    }

    joinIndexBuild();

    // Make sure the new index was successfully built, either by the parallel shell or via
    // createConflictingIndex. We should have the _id index and the 'the_b_1_index' index just built
    // in the parallel shell.
    assert.eq(testColl.getIndexes().length, 2);
}

runConflictingCollectionCreate(testDB, dbName, collName);