/**
 * Verifies that nested SORT_MERGE plans are handled correctly by the SBE stage builder.
 * Intended to reproduce SERVER-61496.
 */
import {getPlanStages} from "jstests/libs/query/analyze_plan.js";

const conn = MongoRunner.runMongod();
const db = conn.getDB("test");

// Disable MatchExpression optimization so that we can craft simple queries that can be answered
// with a nested SORT_MERGE plan. If we allow optimization, then all of the nested $or predicates
// will be optimized away (all child predicates will be raised to children of the top level $or).
assert.commandWorked(
    db.adminCommand({configureFailPoint: "disableMatchExpressionOptimization", mode: "alwaysOn"}));

const collName = jsTestName();

const nDocs = 100;
const idxSpec = {
    a: 1,
    b: -1
};
const queries = [
    // Simple case: one top level SORT_MERGE which has two SORT_MERGE children with 2 IXSCAN
    // leaves each.
    {
        query: {
            $or: [
                {$or: [{a: 2, b: 1}, {a: 4, b: 4}, {a: 2, b: 5}]},
                {$or: [{a: 1, b: 2}, {a: 5, b: 5}]}
            ]
        },
        sortMergeCount: 3
    },

    // SORT_MERGE nested to 3 levels.
    {
        query: {
            $or: [
                {$or: [{$or: [{a: 2, b: 1}, {a: 4, b: 4}]}, {a: 3, b: 3}]},
                {$or: [{a: 1, b: 2}, {a: 5, b: 5}]}
            ]
        },
        sortMergeCount: 4
    }
];

const sortPatterns = [
    // Basic case.
    {
        sort: {a: 1, b: 1},
        cmpFn: (docOne, docTwo) =>
            docOne.a < docTwo.a || (docOne.a === docTwo.a && docOne.b <= docTwo.b)
    },

    // Verify that an index key pattern which doesn't match the order of the sort pattern can still
    // be used to satisfy the sort.
    {
        sort: {b: -1, a: 1},
        cmpFn: (docOne, docTwo) =>
            docOne.b > docTwo.b || (docOne.b === docTwo.b && docOne.a <= docTwo.a)
    }
];

const coll = db[collName];
assert.commandWorked(coll.createIndex(idxSpec));

let docs = [];
for (let i = 0; i < nDocs; ++i) {
    docs.push({
        a: i % 6,
        b: i % 7,
    });
}
assert.commandWorked(coll.insert(docs));

for (const doc of queries) {
    for (const sortPatternDoc of sortPatterns) {
        const query = doc.query;
        const sortPattern = sortPatternDoc.sort;
        const sortFn = sortPatternDoc.cmpFn;

        const expectedSortMergeCount = doc.sortMergeCount;
        const explain = coll.find(query).sort(sortPattern).explain();
        const sortMergeStages = getPlanStages(explain, "SORT_MERGE");
        assert.eq(expectedSortMergeCount,
                  sortMergeStages.length,
                  "Incorrect number of SORT_MERGE stages; explain: " + tojson(explain));

        const docs = coll.find(query).sort(sortPattern).toArray();
        for (let i = 1; i < docs.length; ++i) {
            assert(sortFn(docs[i - 1], docs[i]),
                   "Out of order results for " + tojson(query) +
                       "; sortPattern: " + tojson(sortPattern) + "; results: " + tojson(docs));
        }
    }
}
MongoRunner.stopMongod(conn);