/**
 * Test change stream 'updateDescription' with 'showExpandedEvents'.
 */

import {assertDropAndRecreateCollection} from "jstests/libs/collection_drop_recreate.js";
import {ChangeStreamTest} from "jstests/libs/query/change_stream_util.js";

// Drop and recreate the collections to be used in this set of tests.
assertDropAndRecreateCollection(db, "coll");

const cst = new ChangeStreamTest(db);

const kLargeStr = '*'.repeat(128);
assert.commandWorked(db.coll.insert({
    _id: 100,
    "topLevelArray": [{subArray: [0, [0, [{bottomArray: [1, 2, kLargeStr]}]], 2, 3, kLargeStr]}],
    "arrayForReplacement": [0, 1, 2, 3],
    "arrayForResize": [kLargeStr, 1],
    obj: {
        'sub.obj': {'d.o.t.t.e.d.a.r.r.a.y..': [[{a: {'b.c': 1, field: kLargeStr}}, "truncated"]]}
    },
    'd.o.t.t.e.d.o.b.j.': {'sub.obj': {'b.c': 2}},
    'objectWithNumericField': {'0': {'1': 'numeric', field: kLargeStr}},
    "arrayWithNumericField": [[{'0': "numeric", a: {'b.c': 1}, field: kLargeStr}]],
    "arrayWithDotted.AndNumericFields": [[{'0': [{'1.2': {'a.b': null, c: kLargeStr}}]}]],
}));

const changeStreamCursor = cst.startWatchingChanges(
    {pipeline: [{$changeStream: {showExpandedEvents: true}}], collection: db.coll});

// Test that a path which only contains non-dotted fields and array indices is not reported under
// 'disambiguatedPaths'.
assert.commandWorked(db.coll.update({_id: 100}, {
    $set: {"a": 2, "topLevelArray.0.subArray.1.1.0.bottomArray.2": 3, "arrayForReplacement": [0]}
}));

let expected = {
    documentKey: {_id: 100},
    ns: {db: "test", coll: "coll"},
    operationType: "update",
    updateDescription: {
        updatedFields:
            {"arrayForReplacement": [0], "a": 2, "topLevelArray.0.subArray.1.1.0.bottomArray.2": 3},
        removedFields: [],
        truncatedArrays: [],
        disambiguatedPaths: {}
    },
};
cst.assertNextChangesEqual({cursor: changeStreamCursor, expectedChanges: [expected]});

// Tests that an update modifying a non-array numeric field name is reported as a string rather than
// as an integer under 'disambiguatedPaths'. Array indexes are reported as integers.
assert.commandWorked(
    db.coll.update({_id: 100}, {$set: {"arrayWithNumericField.0.0.1": {"b.c": 1}}}));
expected = {
    documentKey: {_id: 100},
    ns: {db: "test", coll: "coll"},
    operationType: "update",
    updateDescription: {
        updatedFields: {"arrayWithNumericField.0.0.1": {"b.c": 1}},
        removedFields: [],
        truncatedArrays: [],
        disambiguatedPaths: {"arrayWithNumericField.0.0.1": ["arrayWithNumericField", 0, 0, "1"]}
    },
};
cst.assertNextChangesEqual({cursor: changeStreamCursor, expectedChanges: [expected]});

// Tests that an update modifying a non-array numeric field name is reported when no array indices
// or dotted fields are present.
assert.commandWorked(db.coll.update({_id: 100}, {$set: {"objectWithNumericField.0.1": "updated"}}));
expected = {
    documentKey: {_id: 100},
    ns: {db: "test", coll: "coll"},
    operationType: "update",
    updateDescription: {
        updatedFields: {"objectWithNumericField.0.1": "updated"},
        removedFields: [],
        truncatedArrays: [],
        disambiguatedPaths: {"objectWithNumericField.0.1": ["objectWithNumericField", "0", "1"]}
    },
};
cst.assertNextChangesEqual({cursor: changeStreamCursor, expectedChanges: [expected]});

// Tests that an update with $unset array does not report the array under 'disambiguatedPaths'.
assert.commandWorked(db.coll.update({_id: 100}, [{$unset: ["arrayForReplacement"]}]));
expected = {
    documentKey: {_id: 100},
    ns: {db: "test", coll: "coll"},
    operationType: "update",
    updateDescription: {
        updatedFields: {},
        removedFields: ["arrayForReplacement"],
        truncatedArrays: [],
        disambiguatedPaths: {}
    },
};
cst.assertNextChangesEqual({cursor: changeStreamCursor, expectedChanges: [expected]});

// Tests that an update with 'truncatedArrays' does not report the array under 'disambiguatedPaths'.
assert.commandWorked(db.coll.update({_id: 100}, [
    {$replaceWith: {$setField: {field: "arrayForResize", input: '$$ROOT', value: [kLargeStr]}}},
]));
expected = {
    documentKey: {_id: 100},
    ns: {db: "test", coll: "coll"},
    operationType: "update",
    updateDescription: {
        updatedFields: {},
        removedFields: [],
        truncatedArrays: [{field: "arrayForResize", newSize: 1}],
        disambiguatedPaths: {}
    },
};
cst.assertNextChangesEqual({cursor: changeStreamCursor, expectedChanges: [expected]});

// Verify that top-level dotted fields are reported under 'disambiguatedPaths'.
assert.commandWorked(db.coll.update({_id: 100}, [
    {
        $replaceWith:
            {$setField: {field: "d.o.t.t.e.d.o.b.j.", input: '$$ROOT', value: {'subObj': 1}}}
    },
    {$replaceWith: {$setField: {field: "new.Field.", input: '$$ROOT', value: 1}}}
]));
expected = {
    documentKey: {_id: 100},
    ns: {db: "test", coll: "coll"},
    operationType: "update",
    updateDescription: {
        updatedFields: {"d.o.t.t.e.d.o.b.j.": {subObj: 1}, "new.Field.": 1},
        removedFields: [],
        truncatedArrays: [],
        disambiguatedPaths:
            {"d.o.t.t.e.d.o.b.j.": ["d.o.t.t.e.d.o.b.j."], "new.Field.": ["new.Field."]}
    },
};
cst.assertNextChangesEqual({cursor: changeStreamCursor, expectedChanges: [expected]});

// Test that a combination of dotted fields and array indices are reported in 'disambiguatedPaths'.
assert.commandWorked(db.coll.update(
    {_id: 100}, [{
        $set: {
            obj: {
                $setField: {
                    field: "sub.obj",
                    input: '$obj',
                    value: {
                        $literal: {'d.o.t.t.e.d.a.r.r.a.y..': [[{a: {'b.c': 2, field: kLargeStr}}]]}
                    }
                }
            }
        }
    }]));
expected = {
    documentKey: {_id: 100},
    ns: {db: "test", coll: "coll"},
    operationType: "update",
    updateDescription: {
        updatedFields: {"obj.sub.obj.d.o.t.t.e.d.a.r.r.a.y...0.0.a.b.c": 2},
        removedFields: [],
        truncatedArrays: [{field: "obj.sub.obj.d.o.t.t.e.d.a.r.r.a.y...0", newSize: 1}],
        disambiguatedPaths: {
            "obj.sub.obj.d.o.t.t.e.d.a.r.r.a.y...0":
                ["obj", "sub.obj", "d.o.t.t.e.d.a.r.r.a.y..", 0],
            "obj.sub.obj.d.o.t.t.e.d.a.r.r.a.y...0.0.a.b.c":
                ["obj", "sub.obj", "d.o.t.t.e.d.a.r.r.a.y..", 0, 0, "a", "b.c"],
        }
    },
};
cst.assertNextChangesEqual({cursor: changeStreamCursor, expectedChanges: [expected]});

// Test that an update which modifies a path containing dotted, numeric and array index fields
// distinguishes all three in 'disambiguatedPaths'.
assert.commandWorked(
    db.coll.update({_id: 100}, [{
                       $replaceWith: {
                           $setField: {
                               field: "arrayWithDotted.AndNumericFields",
                               input: '$$ROOT',
                               value: {$literal: [[{'0': [{'1.2': {'a.b': true, c: kLargeStr}}]}]]}
                           }
                       }
                   }]));
expected = {
    documentKey: {_id: 100},
    ns: {db: "test", coll: "coll"},
    operationType: "update",
    updateDescription: {
        updatedFields: {"arrayWithDotted.AndNumericFields.0.0.0.0.1.2.a.b": true},
        removedFields: [],
        truncatedArrays: [],
        disambiguatedPaths: {
            "arrayWithDotted.AndNumericFields.0.0.0.0.1.2.a.b":
                ["arrayWithDotted.AndNumericFields", 0, 0, "0", 0, "1.2", "a.b"]
        }
    },
};
cst.assertNextChangesEqual({cursor: changeStreamCursor, expectedChanges: [expected]});

cst.cleanUp();
