#include <test/jtx/TestSuite.h>
#include <test/unit_test/SuiteJournal.h>

#include <xrpld/overlay/Cluster.h>

#include <xrpl/basics/BasicConfig.h>
#include <xrpl/protocol/SecretKey.h>

namespace xrpl {
namespace tests {

class cluster_test : public xrpl::TestSuite
{
    test::SuiteJournal journal_;

public:
    cluster_test() : journal_("cluster_test", *this)
    {
    }

    std::unique_ptr<Cluster>
    create(std::vector<PublicKey> const& nodes)
    {
        auto cluster = std::make_unique<Cluster>(journal_);

        for (auto const& n : nodes)
            cluster->update(n, "Test");

        return cluster;
    }

    static PublicKey
    randomNode()
    {
        return derivePublicKey(KeyType::secp256k1, randomSecretKey());
    }

    void
    testMembership()
    {
        // The servers on the network
        std::vector<PublicKey> network;

        while (network.size() != 128)
            network.push_back(randomNode());

        {
            testcase("Membership: Empty cluster");

            auto c = create({});

            for (auto const& n : network)
                BEAST_EXPECT(!c->member(n));
        }

        {
            testcase("Membership: Non-empty cluster and none present");

            std::vector<PublicKey> cluster;
            while (cluster.size() != 32)
                cluster.push_back(randomNode());

            auto c = create(cluster);

            for (auto const& n : network)
                BEAST_EXPECT(!c->member(n));
        }

        {
            testcase("Membership: Non-empty cluster and some present");

            std::vector<PublicKey> cluster(network.begin(), network.begin() + 16);

            while (cluster.size() != 32)
                cluster.push_back(randomNode());

            auto c = create(cluster);

            for (auto const& n : cluster)
                BEAST_EXPECT(c->member(n));

            for (auto const& n : network)
            {
                auto found = std::find(cluster.begin(), cluster.end(), n);
                BEAST_EXPECT(static_cast<bool>(c->member(n)) == (found != cluster.end()));
            }
        }

        {
            testcase("Membership: Non-empty cluster and all present");

            std::vector<PublicKey> cluster(network.begin(), network.begin() + 32);

            auto c = create(cluster);

            for (auto const& n : cluster)
                BEAST_EXPECT(c->member(n));

            for (auto const& n : network)
            {
                auto found = std::find(cluster.begin(), cluster.end(), n);
                BEAST_EXPECT(static_cast<bool>(c->member(n)) == (found != cluster.end()));
            }
        }
    }

    void
    testUpdating()
    {
        testcase("Updating");

        auto c = create({});

        auto const node = randomNode();
        auto const name = toBase58(TokenType::NodePublic, node);
        std::uint32_t load = 0;
        NetClock::time_point tick = {};

        // Initial update
        BEAST_EXPECT(c->update(node, "", load, tick));
        {
            auto member = c->member(node);
            BEAST_EXPECT(static_cast<bool>(member));
            BEAST_EXPECT(member->empty());  // NOLINT(bugprone-unchecked-optional-access)
        }

        // Updating too quickly: should fail
        BEAST_EXPECT(!c->update(node, name, load, tick));
        {
            auto member = c->member(node);
            BEAST_EXPECT(static_cast<bool>(member));
            BEAST_EXPECT(member->empty());  // NOLINT(bugprone-unchecked-optional-access)
        }

        using namespace std::chrono_literals;

        // Updating the name (empty updates to non-empty)
        tick += 1s;
        BEAST_EXPECT(c->update(node, name, load, tick));
        {
            auto member = c->member(node);
            BEAST_EXPECT(static_cast<bool>(member));
            BEAST_EXPECT(member->compare(name) == 0);  // NOLINT(bugprone-unchecked-optional-access)
        }

        // Updating the name (non-empty doesn't go to empty)
        tick += 1s;
        BEAST_EXPECT(c->update(node, "", load, tick));
        {
            auto member = c->member(node);
            BEAST_EXPECT(static_cast<bool>(member));
            BEAST_EXPECT(member->compare(name) == 0);  // NOLINT(bugprone-unchecked-optional-access)
        }

        // Updating the name (non-empty updates to new non-empty)
        tick += 1s;
        BEAST_EXPECT(c->update(node, "test", load, tick));
        {
            auto member = c->member(node);
            BEAST_EXPECT(static_cast<bool>(member));
            BEAST_EXPECT(
                member->compare("test") == 0);  // NOLINT(bugprone-unchecked-optional-access)
        }
    }

    void
    testConfigLoad()
    {
        testcase("Config Load");

        auto c = std::make_unique<Cluster>(journal_);

        // The servers on the network
        std::vector<PublicKey> network;

        while (network.size() != 8)
            network.push_back(randomNode());

        auto format = [](PublicKey const& publicKey, char const* comment = nullptr) {
            auto ret = toBase58(TokenType::NodePublic, publicKey);

            if (comment)
                ret += comment;

            return ret;
        };

        Section s1;

        // Correct (empty) configuration
        BEAST_EXPECT(c->load(s1));
        BEAST_EXPECT(c->size() == 0);

        // Correct configuration
        s1.append(format(network[0]));
        s1.append(format(network[1], "    "));
        s1.append(format(network[2], " Comment"));
        s1.append(format(network[3], " Multi Word Comment"));
        s1.append(format(network[4], "  Leading Whitespace"));
        s1.append(format(network[5], " Trailing Whitespace  "));
        s1.append(format(network[6], "  Leading & Trailing Whitespace  "));
        s1.append(format(network[7], "  Leading,  Trailing  &  Internal  Whitespace  "));

        BEAST_EXPECT(c->load(s1));

        for (auto const& n : network)
            BEAST_EXPECT(c->member(n));

        // Incorrect configurations
        Section s2;
        s2.append("NotAPublicKey");
        BEAST_EXPECT(!c->load(s2));

        Section s3;
        s3.append(format(network[0], "!"));
        BEAST_EXPECT(!c->load(s3));

        Section s4;
        s4.append(format(network[0], "!  Comment"));
        BEAST_EXPECT(!c->load(s4));

        // Check if we properly terminate when we encounter
        // a malformed or unparsable entry:
        auto const node1 = randomNode();
        auto const node2 = randomNode();

        Section s5;
        s5.append(format(node1, "XXX"));
        s5.append(format(node2));
        BEAST_EXPECT(!c->load(s5));
        BEAST_EXPECT(!c->member(node1));
        BEAST_EXPECT(!c->member(node2));
    }

    void
    run() override
    {
        testMembership();
        testUpdating();
        testConfigLoad();
    }
};

BEAST_DEFINE_TESTSUITE(cluster, overlay, xrpl);

}  // namespace tests
}  // namespace xrpl
