// This test verifies that a nested $lookup pipeline can have a total combined depth of 20 but no
// greater.
// @tags: [requires_sharding]

import {assertErrorCode} from "jstests/aggregation/extras/utils.js";
import {assertDropCollection} from "jstests/libs/collection_drop_recreate.js";
import {ShardingTest} from "jstests/libs/shardingtest.js";

function generateNestedPipeline(foreignCollName, numLevels) {
    let pipeline = [{"$lookup": {pipeline: [], from: foreignCollName, as: "same"}}];

    for (let level = 1; level < numLevels; level++) {
        pipeline = [{"$lookup": {pipeline: pipeline, from: foreignCollName, as: "same"}}];
    }

    return pipeline;
}

function runTest(lookup) {
    const db = null;  // Using the db variable is banned in this function.
    const lookupName = lookup.getName();

    // Deeply nested $lookup pipeline. Confirm that we can execute an aggregation with nested
    // $lookup sub-pipelines up to the maximum depth, but not beyond.
    let nestedPipeline = generateNestedPipeline("lookup", 20);
    assert.commandWorked(
        lookup.getDB().runCommand({aggregate: lookupName, pipeline: nestedPipeline, cursor: {}}));

    nestedPipeline = generateNestedPipeline("lookup", 21);
    assertErrorCode(lookup, nestedPipeline, ErrorCodes.MaxSubPipelineDepthExceeded);

    // Confirm that maximum $lookup sub-pipeline depth is respected when aggregating views whose
    // combined nesting depth exceeds the limit.
    nestedPipeline = generateNestedPipeline(lookupName, 10);

    assertDropCollection(lookup.getDB(), "view1");
    assert.commandWorked(
        lookup.getDB().runCommand({create: "view1", viewOn: lookupName, pipeline: nestedPipeline}));

    nestedPipeline = generateNestedPipeline("view1", 10);
    assertDropCollection(lookup.getDB(), "view2");
    assert.commandWorked(
        lookup.getDB().runCommand({create: "view2", viewOn: "view1", pipeline: nestedPipeline}));

    // Confirm that a composite sub-pipeline depth of 20 is allowed.
    assert.commandWorked(lookup.getDB().runCommand({aggregate: "view2", pipeline: [], cursor: {}}));

    const pipelineWhichExceedsNestingLimit = generateNestedPipeline("view2", 1);
    assertDropCollection(lookup.getDB(), "view3");
    assert.commandWorked(lookup.getDB().runCommand(
        {create: "view3", viewOn: "view2", pipeline: pipelineWhichExceedsNestingLimit}));

    // Confirm that a composite sub-pipeline depth greater than 20 fails.
    assertErrorCode(lookup.getDB().view3, [], ErrorCodes.MaxSubPipelineDepthExceeded);
}

// Run tests on single node.
const standalone = MongoRunner.runMongod();
runTest(standalone.getDB("test").lookup);

MongoRunner.stopMongod(standalone);

// Run tests in a sharded environment. We must set up sharding explicitly, because
// implicitly_shard_accessed_collections.js will attempt to shard views when they are accessed.
const sharded = new ShardingTest({mongos: 1, shards: 2});

assert(sharded.adminCommand({enableSharding: "test"}));
assert(sharded.adminCommand({shardCollection: "test.lookup", key: {_id: 'hashed'}}));

runTest(sharded.getDB('test').lookup);

sharded.stop();
