/**
 * Tests that only measurements with a binary identical meta field are included in the same bucket
 * in a time-series collection.
 * @tags: [
 *   assumes_no_implicit_collection_creation_after_drop,
 *   does_not_support_stepdowns,
 *   does_not_support_transactions,
 *   requires_fcv_51,
 *   requires_getmore,
 * ]
 */
(function() {
"use strict";

load("jstests/core/timeseries/libs/timeseries.js");

TimeseriesTest.run((insert) => {
    const collNamePrefix = 'timeseries_metadata_';

    const timeFieldName = 'time';
    const metaFieldName = 'meta';

    let collCount = 0;

    /**
     * Accepts two lists of measurements. Measurements in each list should contain the same value
     * for the metadata field (but distinct from the other list). We should see two buckets created
     * in the time-series collection.
     */
    const runTest = function(docsBucketA, docsBucketB) {
        const coll = db.getCollection(collNamePrefix + collCount++);
        const bucketsColl = db.getCollection('system.buckets.' + coll.getName());
        coll.drop();

        jsTestLog('Running test: collection: ' + coll.getFullName() +
                  '; bucket collection: ' + bucketsColl.getFullName() +
                  '; bucketA: ' + tojson(docsBucketA) + '; bucketB: ' + tojson(docsBucketB));

        assert.commandWorked(db.createCollection(
            coll.getName(), {timeseries: {timeField: timeFieldName, metaField: metaFieldName}}));
        assert.contains(bucketsColl.getName(), db.getCollectionNames());

        let docs = docsBucketA.concat(docsBucketB);
        assert.commandWorked(insert(coll, docs), 'failed to insert docs: ' + tojson(docs));

        // Check view.
        const viewDocs = coll.find({}).sort({_id: 1}).toArray();
        assert.eq(docs.length, viewDocs.length, viewDocs);
        for (let i = 0; i < docs.length; i++) {
            assert.docEq(docs[i], viewDocs[i], 'unexpected doc from view: ' + i);
        }

        // Check bucket collection.
        const bucketDocs = bucketsColl.find().sort({'control.min._id': 1}).toArray();
        assert.eq(2, bucketDocs.length, bucketDocs);

        // Check both buckets.
        // First bucket should contain documents specified in 'bucketA'.
        assert.eq(docsBucketA.length,
                  Object.keys(bucketDocs[0].data[timeFieldName]).length,
                  'invalid number of measurements in first bucket: ' + tojson(bucketDocs[0]));
        if (docsBucketA[0].hasOwnProperty(metaFieldName)) {
            assert.eq(docsBucketA[0][metaFieldName],
                      bucketDocs[0].meta,
                      'invalid meta in first bucket: ' + tojson(bucketDocs[0]));
            assert(!bucketDocs[0].data.hasOwnProperty(metaFieldName),
                   'unexpected metadata in first bucket data: ' + tojson(bucketDocs[0]));
        }

        // Second bucket should contain documents specified in 'bucketB'.
        assert.eq(docsBucketB.length,
                  Object.keys(bucketDocs[1].data[timeFieldName]).length,
                  'invalid number of measurements in second bucket: ' + tojson(bucketDocs[1]));
        if (docsBucketB[0].hasOwnProperty(metaFieldName)) {
            assert.eq(docsBucketB[0][metaFieldName],
                      bucketDocs[1].meta,
                      'invalid meta in second bucket: ' + tojson(bucketDocs[1]));
            assert(!bucketDocs[1].data.hasOwnProperty(metaFieldName),
                   'unexpected metadata in second bucket data: ' + tojson(bucketDocs[1]));
        }
    };

    // Ensure _id order of docs in the bucket collection by using constant times.
    const t = [
        ISODate("2020-11-26T00:00:00.000Z"),
        ISODate("2020-11-26T00:10:00.000Z"),
        ISODate("2020-11-26T00:20:00.000Z"),
        ISODate("2020-11-26T00:30:00.000Z"),
    ];

    runTest(
        [
            {_id: 0, time: t[0], meta: 'a', x: 0},
            {_id: 1, time: t[1], meta: 'a', x: 10},
        ],
        [
            {_id: 2, time: t[2], meta: 'b', x: 20},
            {_id: 3, time: t[3], meta: 'b', x: 30},
        ]);

    runTest(
        [
            {_id: 0, time: t[0], meta: null, x: 0},
            {_id: 1, time: t[1], meta: null, x: 10},
        ],
        [
            {_id: 2, time: t[2], x: 20},
            {_id: 3, time: t[3], x: 30},
        ]);

    runTest(
        [
            {_id: 0, time: t[0], meta: null, x: 0},
            {_id: 1, time: t[1], meta: null, x: 10},
        ],
        [
            {_id: 2, time: t[2], meta: 123, x: 20},
            {_id: 3, time: t[3], meta: 123, x: 30},
        ]);

    runTest(
        // Metadata field contains an array.
        [
            {_id: 0, time: t[0], meta: [1, 2, 3], x: 0},
            {_id: 1, time: t[1], meta: [1, 2, 3], x: 10},
        ],
        [
            {_id: 2, time: t[2], x: 20},
            {_id: 3, time: t[3], x: 30},
        ]);

    runTest(
        // Metadata field contains an object. Its ordering does not affect which bucket is used.
        [
            {_id: 0, time: t[0], meta: {a: 1, b: 1}, x: 0},
            {_id: 1, time: t[1], meta: {a: 1, b: 1}, x: 10},
            {_id: 2, time: t[2], meta: {b: 1, a: 1}, x: 20},
        ],
        [
            {_id: 3, time: t[3], meta: {a: 1}, x: 30},
        ]);

    runTest(
        // Metadata field contains an array within an object.
        [
            {_id: 0, time: t[0], meta: {a: [{b: 1, c: 0}]}, x: 0},
            {_id: 1, time: t[1], meta: {a: [{c: 0, b: 1}]}, x: 10},
        ],
        [
            {_id: 2, time: t[2], meta: {a: [{b: 2, c: 0}]}, x: 20},
            {_id: 3, time: t[3], meta: {a: [{c: 0, b: 2}]}, x: 30},
        ]);

    runTest(
        // Metadata field contains a nested array.
        [
            {_id: 0, time: t[0], meta: {a: [{b: 1, c: 0}, [{e: 1, f: 0}]]}, x: 0},
            {_id: 1, time: t[1], meta: {a: [{c: 0, b: 1}, [{f: 0, e: 1}]]}, x: 10},
        ],
        [
            {_id: 2, time: t[2], meta: {a: [[{e: 1, f: 0}], {b: 1, c: 0}]}, x: 20},
            {_id: 3, time: t[3], meta: {a: [[{f: 0, e: 1}], {c: 0, b: 1}]}, x: 30},
        ]);

    runTest(
        // Metadata field contains an array.
        [
            {_id: 0, time: t[0], meta: {a: [1, 2, 3]}, x: 0},
            {_id: 1, time: t[1], meta: {a: [1, 2, 3]}, x: 10},
        ],
        [
            {_id: 2, time: t[2], meta: {a: [2, 1, 3]}, x: 20},
            {_id: 3, time: t[3], meta: {a: [2, 1, 3]}, x: 30},
        ]);
});
})();
