/**
 * Tests the basics of the "directShardConnections" serverStatus metrics.
 *
 * @tags: [
 *   requires_fcv_73,
 *    # TODO (SERVER-88127): Re-enable this test or add an explanation why it is incompatible.
 *    embedded_router_incompatible,
 * ]
 */

import {configureFailPoint} from "jstests/libs/fail_point_util.js";
import {Thread} from "jstests/libs/parallelTester.js";
import {ShardingTest} from "jstests/libs/shardingtest.js";

function assertNoDirectShardConnectionsMetrics(conn) {
    const res = conn.adminCommand({serverStatus: 1});
    assert(!res.hasOwnProperty("directShardConnections"), res);
}

function assertDirectShardConnectionsMetrics(conn, expected) {
    const res = conn.adminCommand({serverStatus: 1});
    assert(res.hasOwnProperty("directShardConnections"), res);
    assert.eq(res.directShardConnections.current,
              expected.current,
              {expected, actual: res.directShardConnections});
    assert.eq(res.directShardConnections.totalCreated,
              expected.totalCreated,
              {expected, actual: res.directShardConnections});
}

function assertSoonDirectShardConnectionsMetrics(conn, expected) {
    let numTries = 0;
    assert.soon(() => {
        numTries++;
        const res = conn.adminCommand({serverStatus: 1});
        assert(res.hasOwnProperty("directShardConnections"), res);
        if (res.directShardConnections.current != expected.current ||
            res.directShardConnections.totalCreated != expected.totalCreated) {
            if (numTries % 100 == 0) {
                jsTest.log("Still waiting for direct shard connections metrics " +
                           tojson({expected, actual: res.directShardConnections}));
            }
            return false;
        }
        return true;
    });
}

const st = new ShardingTest({mongos: 1, shards: 1});
const shard0Primary = st.rs0.getPrimary();

const dbName = "testDb";
const collName = "testColl";
const ns = dbName + "." + collName;

jsTest.log("Get the initial metrics");
assertNoDirectShardConnectionsMetrics(st.s);
const res = shard0Primary.adminCommand({serverStatus: 1});
assert(res.hasOwnProperty("directShardConnections"), res);
const currentDirectShardConnections = res.directShardConnections;

jsTest.log("Test metrics after creating a new internal connection");
// The mongos will need to create internal connections to shard0's primary to do the write.
assert.commandWorked(st.s.getCollection(ns).insert({_id: 1}));
assertNoDirectShardConnectionsMetrics(st.s);
assertDirectShardConnectionsMetrics(shard0Primary, currentDirectShardConnections);

jsTest.log("Test metrics after reusing on an existing external connection");
assert.neq(shard0Primary.getCollection(ns).findOne({_id: 1}), null);
assertNoDirectShardConnectionsMetrics(st.s);
assertDirectShardConnectionsMetrics(shard0Primary, currentDirectShardConnections);

jsTest.log("Test metrics after creating a new connection");
const newShard0Primary = new Mongo(shard0Primary.host);
assert.neq(newShard0Primary.getCollection(ns).findOne({_id: 1}), null);
currentDirectShardConnections.current++;
currentDirectShardConnections.totalCreated++;
assertNoDirectShardConnectionsMetrics(st.s);
assertDirectShardConnectionsMetrics(newShard0Primary, currentDirectShardConnections);

jsTest.log("Test metrics after creating another new external connection");
const runInsertCommand = (host, dbName, collName) => {
    const conn = new Mongo(host);
    const ns = dbName + "." + collName;
    assert.commandWorked(conn.getCollection(ns).insert({_id: 2}));
    conn.close();
};

const fp = configureFailPoint(shard0Primary, "hangInsertBeforeWrite", {ns});
const insertThread = new Thread(runInsertCommand, shard0Primary.host, dbName, collName);
insertThread.start();
fp.wait();

currentDirectShardConnections.totalCreated++;
currentDirectShardConnections.current++;
assertNoDirectShardConnectionsMetrics(st.s);
assertDirectShardConnectionsMetrics(shard0Primary, currentDirectShardConnections);

fp.off();
insertThread.join();

jsTest.log("Test metrics after closing the new external connection");
currentDirectShardConnections.current--;
assertNoDirectShardConnectionsMetrics(st.s);
// Use assert.soon since there can be a lag for when the connection is destroyed on the server side.
assertSoonDirectShardConnectionsMetrics(shard0Primary, currentDirectShardConnections);

// TODO (SERVER-87512): Connect to shard0's primary on the router port and verify that
// its serverStatus metrics do not contain "directShardConnections" metrics, and that the
// "directShardConnections" metrics on the shard port does not count this connection.

// jsTest.log("Test metrics after creating a external connection on the router port").
// const shard0PrimaryRouter = ...;
// assert.commandWorked(shard0PrimaryRouter.getCollection(ns).insert({_id: 3}));
// assertNoDirectShardConnectionsMetrics(st.s);
// assertNoDirectShardConnectionsMetrics(shard0PrimaryRouter);
// assertDirectShardConnectionsMetrics(shard0Primary, currentDirectShardConnections);

st.stop();
