/**
 * Verifies that haystack indexes are removed during the upgrade process from 4.4 to 4.9 for a
 * standalone and a replica set.
 *
 * TODO SERVER-51871: Since this test is specific to the upgrade process from 4.4 to 4.9/5.0, it
 * can be deleted once 5.0 becomes last-lts.
 */
(function() {
"use strict";
load("jstests/multiVersion/libs/multi_rs.js");         // For 'upgradeSet()'
load("jstests/multiVersion/libs/multi_cluster.js");    // For 'upgradeCluster()'
load('jstests/multiVersion/libs/verify_versions.js');  // For 'assert.binVersion()'
load('jstests/noPassthrough/libs/index_build.js');     // For 'assertIndexes()'

const dbName = "test";
const collName = jsTestName();
const geoIndexKey = {
    loc: "geoHaystack",
    x: 1
};
const geoIndexName = "geo";
const nonGeoIndexKey = {
    y: 1
};
const nonGeoIndexName = "y";

// Set of indexes to insert.
const indexList = ["_id_", nonGeoIndexName, geoIndexName];

// Set of indexes that will be present after upgrade is complete.
const nonGeoIndexList = ["_id_", nonGeoIndexName];

function insertDocuments(coll) {
    const documentList =
        [{_id: 0, loc: [1, 2], x: 'foo', y: 2}, {_id: 1, loc: [1.5, 1.5], x: 'bar', y: 1}];
    assert.commandWorked(coll.insert(documentList));
}

function createIndexes(coll) {
    assert.commandWorked(coll.createIndex(geoIndexKey, {name: geoIndexName, bucketSize: 1}));
    assert.commandWorked(coll.createIndex(nonGeoIndexKey, {name: nonGeoIndexName}));
}

// Verify that haystack indexes are deleted when upgrading a standalone.
function runStandaloneTest() {
    // Set up a v4.4 mongod.
    const dbPath = MongoRunner.dataPath + "/delete_haystack";
    let mongo = MongoRunner.runMongod({dbpath: dbPath, binVersion: "last-lts"});
    assert.neq(null, mongo, "mongod was unable to start up");
    let testDB = mongo.getDB(dbName);
    let coll = testDB[collName];
    insertDocuments(coll);
    createIndexes(coll);
    IndexBuildTest.assertIndexes(testDB[collName], indexList.length, indexList);
    const validate = assert.commandWorked(coll.validate({full: true}));
    assert.eq(true, validate.valid);
    MongoRunner.stopMongod(mongo);

    // Restart the mongod in the latest version.
    mongo = MongoRunner.runMongod(
        {dbpath: dbPath, binVersion: "latest", restart: true, cleanData: false});
    assert.neq(null, mongo, "mongod was unable to start up");
    testDB = mongo.getDB(dbName);
    coll = testDB[collName];

    // The haystack index should still be present before the FCV is set.
    IndexBuildTest.assertIndexes(coll, indexList.length, indexList);

    // Set the FCV.
    const adminDB = mongo.getDB("admin");
    checkFCV(adminDB, lastLTSFCV);
    assert.commandWorked(mongo.adminCommand({setFeatureCompatibilityVersion: latestFCV}));
    checkFCV(adminDB, latestFCV);

    // The haystack index should no longer be present after the FCV is set.
    IndexBuildTest.assertIndexes(coll, nonGeoIndexList.length, nonGeoIndexList);
    MongoRunner.stopMongod(mongo);
}

/**
 * Verifies that every node in 'replSetTest' has the indexes in 'expectedIndexes'.
 */
function verifyIndexesPresentOnAllNodes(replSetTest, expectedIndexes) {
    replSetTest.awaitNodesAgreeOnPrimary();
    for (const node of [replSetTest.getPrimary(), replSetTest.getSecondary()]) {
        const db = node.getDB(dbName);
        const coll = db[collName];
        IndexBuildTest.assertIndexes(coll, expectedIndexes.length, expectedIndexes);
    }
}

// Verify that haystack indexes get deleted when upgrading a replica set.
function runReplicaSetTest() {
    // Set up a replica-set in v4.4.
    const rst = new ReplSetTest({nodes: 2, nodeOptions: {binVersion: "last-lts"}});
    rst.startSet();
    rst.initiate();

    const initialPrimary = rst.getPrimary();
    const primaryDB = initialPrimary.getDB(dbName);
    const primaryColl = primaryDB[collName];
    insertDocuments(primaryColl);
    createIndexes(primaryColl);

    // Wait until both nodes finish inserting the documents and building the index.
    rst.awaitReplication();

    verifyIndexesPresentOnAllNodes(rst, indexList);

    // Upgrade the replica set.
    rst.upgradeSet({binVersion: "latest"});
    rst.awaitNodesAgreeOnPrimary();

    // Verify that all nodes are in the latest version.
    for (const node of rst.nodes) {
        assert.binVersion(node, "latest");
    }

    verifyIndexesPresentOnAllNodes(rst, indexList);

    // Set the FCV.
    const upgradedPrimary = rst.getPrimary();
    const adminDB = upgradedPrimary.getDB("admin");
    checkFCV(adminDB, lastLTSFCV);
    assert.commandWorked(adminDB.runCommand({setFeatureCompatibilityVersion: latestFCV}));
    checkFCV(adminDB, latestFCV);

    // Neither the primary nor the secondary should have the haystack index.
    verifyIndexesPresentOnAllNodes(rst, nonGeoIndexList);

    rst.stopSet();
}

// Even though the 'geoSearch' command is not allowed on sharded clusters, haystack indexes can
// still be created on a sharded cluster. As such, we verify that haystack indexes get deleted
// when upgrading a sharded cluster.
function runShardingTest() {
    // Set up a sharded cluster in v4.4.
    const st = new ShardingTest({
        shards: 2,
        rs: {nodes: 2, binVersion: "last-lts"},
        other: {mongosOptions: {binVersion: "last-lts"}, configOptions: {binVersion: "last-lts"}}
    });

    let mongos = st.s;
    const ns = dbName + "." + collName;

    // Create a sharded collection with two chunks, one on each shard: [-inf, 1), [1, inf). This
    // guarantees that each shard will have one of the two documents being inserted and both shards
    // will create a geoHaystack index.
    assert.commandWorked(mongos.adminCommand({enableSharding: dbName}));
    assert.commandWorked(mongos.adminCommand({movePrimary: dbName, to: st.shard0.shardName}));
    assert.commandWorked(mongos.adminCommand({shardCollection: ns, key: {_id: 1}}));
    assert.commandWorked(mongos.adminCommand({split: ns, middle: {_id: 1}}));

    // Move the [1, inf) chunk to shard 1.
    assert.commandWorked(mongos.adminCommand(
        {moveChunk: ns, find: {_id: 1}, to: st.shard1.shardName, _waitForDelete: true}));

    const db = mongos.getDB(dbName);
    const coll = db[collName];
    insertDocuments(coll);
    createIndexes(coll);

    // Wait for both shards to finish replicating their document and building the indexes.
    st.rs0.awaitReplication();
    st.rs1.awaitReplication();

    /**
     * Verify that each shard has each index in 'expectedIndexes'.
     */
    function verifyIndexesOnAllShards(expectedIndexes) {
        for (const shard of [st.rs0, st.rs1]) {
            verifyIndexesPresentOnAllNodes(shard, expectedIndexes);
        }
    }

    verifyIndexesOnAllShards(indexList);

    // Upgrade the shards and the config servers.
    st.upgradeCluster("latest", {upgradeShards: true, upgradeConfigs: true, upgradeMongos: false});
    st.waitUntilStable();

    // Indexes should still be present.
    verifyIndexesOnAllShards(indexList);

    // Upgrade the mongos.
    st.upgradeCluster("latest", {upgradeShards: false, upgradeConfigs: false, upgradeMongos: true});
    st.waitUntilStable();

    /**
     * Verifies that the FCV across 'st' matches 'targetFCV'.
     */
    function checkClusterFCV(targetFCV) {
        checkFCV(st.configRS.getPrimary().getDB("admin"), targetFCV);
        checkFCV(st.shard0.getDB("admin"), targetFCV);
        checkFCV(st.shard1.getDB("admin"), targetFCV);
    }

    // Set the FCV.
    mongos = st.s;
    const adminDB = mongos.getDB("admin");
    checkClusterFCV(lastLTSFCV);
    verifyIndexesOnAllShards(indexList);
    assert.commandWorked(adminDB.runCommand({setFeatureCompatibilityVersion: latestFCV}));
    checkClusterFCV(latestFCV);

    // None of the nodes in the cluster should have the haystack index.
    verifyIndexesOnAllShards(nonGeoIndexList);

    st.stop();
}

runStandaloneTest();
runReplicaSetTest();
runShardingTest();
})();