/**
 * Validates the operationTime value in the command response depends on the read/writeConcern of the
 * the read/write commmand that produced it.
 */
(function() {
    "use strict";

    load("jstests/replsets/rslib.js");           // For startSetIfSupportsReadMajority.
    load("jstests/libs/write_concern_util.js");  // For stopReplicationOnSecondaries,
                                                 // restartReplicationOnSecondaries
    var name = "operation_time_read_and_write_concern";

    var replTest =
        new ReplSetTest({name: name, nodes: 3, nodeOptions: {enableMajorityReadConcern: ""}});

    if (!startSetIfSupportsReadMajority(replTest)) {
        jsTestLog("Skipping test since storage engine doesn't support majority read concern.");
        return;
    }
    replTest.initiate();

    var res;
    var testDB = replTest.getPrimary().getDB(name);
    var collectionName = "foo";

    // readConcern level majority:
    // operationTime is the logical time of the last committed op in the oplog.
    jsTestLog("Testing operationTime for readConcern level majority with afterClusterTime.");
    var majorityDoc = {_id: 10, x: 1};
    var localDoc = {_id: 15, x: 2};

    res = assert.commandWorked(testDB.runCommand(
        {insert: collectionName, documents: [majorityDoc], writeConcern: {w: "majority"}}));
    var majorityWriteOperationTime = res.operationTime;

    stopReplicationOnSecondaries(replTest);

    res = assert.commandWorked(
        testDB.runCommand({insert: collectionName, documents: [localDoc], writeConcern: {w: 1}}));
    var localWriteOperationTime = res.operationTime;

    assert(localWriteOperationTime > majorityWriteOperationTime);

    res = assert.commandWorked(testDB.runCommand({
        find: collectionName,
        readConcern: {level: "majority", afterClusterTime: majorityWriteOperationTime}
    }));
    var majorityReadOperationTime = res.operationTime;

    assert.eq(res.cursor.firstBatch,
              [majorityDoc],
              "only the committed document, " + tojson(majorityDoc) +
                  ", should be returned for the majority read with afterClusterTime: " +
                  majorityWriteOperationTime);
    assert.eq(majorityReadOperationTime,
              majorityWriteOperationTime,
              "the operationTime of the majority read, " + majorityReadOperationTime +
                  ", should be the logical time of the last committed op in the oplog, " +
                  majorityWriteOperationTime);

    // Validate that after replication, the local write data is now returned by the same query.
    restartReplicationOnSecondaries(replTest);
    replTest.awaitLastOpCommitted();

    res = assert.commandWorked(testDB.runCommand({
        find: collectionName,
        sort: {_id: 1},  // So the order of the documents is defined for testing.
        readConcern: {level: "majority", afterClusterTime: majorityWriteOperationTime}
    }));
    var secondMajorityReadOperationTime = res.operationTime;

    assert.eq(res.cursor.firstBatch,
              [majorityDoc, localDoc],
              "expected both inserted documents, " + tojson([majorityDoc, localDoc]) +
                  ", to be returned for the second majority read with afterClusterTime: " +
                  majorityWriteOperationTime);
    assert.eq(secondMajorityReadOperationTime,
              localWriteOperationTime,
              "the operationTime of the second majority read, " + secondMajorityReadOperationTime +
                  ", should be the logical time of the replicated local write, " +
                  localWriteOperationTime);

    // readConcern level local:
    // Not currently supported.
    jsTestLog("Verifying readConcern local with afterClusterTime is not supported.");
    res = assert.commandFailedWithCode(
        testDB.runCommand({
            find: collectionName,
            filter: localDoc,
            readConcern: {level: "local", afterClusterTime: majorityReadOperationTime}
        }),
        ErrorCodes.InvalidOptions,
        "local reads with afterClusterTime are not supported and should not be allowed");

    // writeConcern level majority:
    // operationTime is the logical time of the write if it succeeds, or of the previous successful
    // write at the time the write was determined to have failed, or a no-op.
    jsTestLog("Testing operationTime for writeConcern level majority.");
    var successfulDoc = {_id: 1000, y: 1};
    var failedDoc = {_id: 1000, y: 2};

    res = assert.commandWorked(testDB.runCommand(
        {insert: collectionName, documents: [successfulDoc], writeConcern: {w: "majority"}}));
    var majorityWriteOperationTime = res.operationTime;

    stopReplicationOnSecondaries(replTest);

    res = testDB.runCommand(
        {insert: collectionName, documents: [failedDoc], writeConcern: {w: "majority"}});
    assert.eq(res.writeErrors[0].code, ErrorCodes.DuplicateKey);
    var failedWriteOperationTime = res.operationTime;

    assert.eq(
        failedWriteOperationTime,
        majorityWriteOperationTime,
        "the operationTime of the failed majority write, " + failedWriteOperationTime +
            ", should be the logical time of the last successful write at the time it failed, " +
            majorityWriteOperationTime);

    replTest.stopSet();
})();
