#include <test/jtx.h>

#include <xrpld/rpc/detail/Handler.h>

#include <xrpl/beast/unit_test.h>

#include <chrono>
#include <iostream>
#include <limits>
#include <random>
// cspell: words stdev

namespace xrpl::test {

// NOTE: there should be no need for this function;
// `std::cout << some_duration` should just work if built with a compliant
// C++20 compiler. Sadly, we are not using one, as of today
// TODO: remove this operator<< overload when we bump compiler version
std::ostream&
operator<<(std::ostream& os, std::chrono::nanoseconds ns)
{
    return (os << ns.count() << "ns");
}

// NOTE This is a rather naive effort at a microbenchmark. Ideally we want
// Google Benchmark, or something similar. Also, this actually does not belong
// to unit tests, as it makes little sense to run it in conditions very
// dissimilar to how rippled will normally work.
// TODO as https://github.com/XRPLF/rippled/issues/4765

class Handler_test : public beast::unit_test::suite
{
    auto
    time(std::size_t n, auto f, auto prng) -> auto
    {
        using clock = std::chrono::steady_clock;
        assert(n > 0);
        double sum = 0;
        double sum_squared = 0;
        std::size_t j = 0;
        while (j < n)
        {
            // Generate 100 inputs upfront, separated from the inner loop
            std::array<decltype(prng()), 100> inputs = {};
            for (auto& i : inputs)
            {
                i = prng();
            }

            // Take 100 samples, then sort and throw away 35 from each end,
            // using only middle 30. This helps to reduce measurement noise.
            std::array<long, 100> samples = {};
            for (std::size_t k = 0; k < 100; ++k)
            {
                auto start = std::chrono::steady_clock::now();
                f(inputs[k]);
                samples[k] = (std::chrono::steady_clock::now() - start).count();
            }

            std::sort(samples.begin(), samples.end());
            for (std::size_t k = 35; k < 65; ++k)
            {
                j += 1;
                sum += samples[k];
                sum_squared += (samples[k] * samples[k]);
            }
        }

        double const mean_squared = (sum * sum) / (j * j);
        return std::make_tuple(
            clock::duration{static_cast<long>(sum / j)},
            clock::duration{static_cast<long>(std::sqrt((sum_squared / j) - mean_squared))},
            j);
    }

    void
    reportLookupPerformance()
    {
        testcase("Handler lookup performance");

        std::random_device dev;
        std::ranlux48 prng(dev());

        std::vector<char const*> names = test::jtx::make_vector(xrpl::RPC::getHandlerNames());

        std::uniform_int_distribution<std::size_t> distr{0, names.size() - 1};

        std::size_t dummy = 0;
        auto const [mean, stdev, n] = time(
            1'000'000,
            [&](std::size_t i) {
                auto const d = RPC::getHandler(1, false, names[i]);
                dummy = dummy + i + (int)d->role_;
            },
            [&]() -> std::size_t { return distr(prng); });

        std::cout << "mean=" << mean << " stdev=" << stdev << " N=" << n << '\n';

        BEAST_EXPECT(dummy != 0);
    }

public:
    void
    run() override
    {
        reportLookupPerformance();
    }
};

BEAST_DEFINE_TESTSUITE_MANUAL(Handler, rpc, xrpl);

}  // namespace xrpl::test
