#include <catch2/catch_test_macros.hpp>

#include "TestPeerNode.hpp"
#include "QuicrPeerLink.hpp"
#include "WorldStepProcessor.hpp"

#include <barrier>
#include <limits>
#include <memory>
#include <spdlog/spdlog.h>
#include <thread>
#include <vector>

using namespace tw::p2p;

// ── constants ─────────────────────────────────────────────────────────────────

static constexpr int      NUM_PEERS   = 5;
static constexpr uint16_t BASE_PORT   = 7600;
static constexpr int      NUM_TICKS   = 120; // 2 s at 60 Hz
static constexpr auto     TICK_PERIOD =
    std::chrono::microseconds(static_cast<int>(WorldStepProcessor::FIXED_DELTA_S * 1e6));

static const char* REGISTRY_PATH = "/tmp/tw_p2p_bench_registry.csv";

// ── helpers ───────────────────────────────────────────────────────────────────

static void cleanup_files() {
    ::unlink(REGISTRY_PATH);
    for (int i = 1; i <= NUM_PEERS; ++i) {
        std::string path = std::string("/tmp/peer_node_") + std::to_string(i) + ".csv";
        ::unlink(path.c_str());
    }
}

// ── benchmark test ────────────────────────────────────────────────────────────

TEST_CASE("10 peers synchronize and write per-entity CSV", "[p2p][benchmark]") {
    cleanup_files();

    // Phase barrier: all nodes must register before any node connects.
    std::barrier registered(NUM_PEERS);

    struct Result { uint64_t stepped = 0; uint64_t rollbacks = 0; };
    std::vector<Result> results(NUM_PEERS);

    auto run_node = [&](int idx) {
        const uint32_t id   = static_cast<uint32_t>(idx + 1);
        const uint16_t port = BASE_PORT + static_cast<uint16_t>(idx);

        auto link = std::make_unique<QuicrPeerLink>(id, port);
        std::string csv = "/tmp/peer_node_" + std::to_string(id) + ".quicr.csv";
        auto node = std::make_unique<TestPeerNode>(id, port, std::move(link), REGISTRY_PATH, csv);

        node->register_self();
        registered.arrive_and_wait(); // block until every peer has registered

        node->discover_and_connect();
        node->wait_for_connections(NUM_PEERS - 1, std::chrono::milliseconds(3000));

        auto deadline = std::chrono::steady_clock::now();
        for (int i = 0; i < NUM_TICKS; ++i) {
            deadline += TICK_PERIOD;
            node->tick();
            std::this_thread::sleep_until(deadline);
        }

        results[idx].stepped   = node->stepped_frames();
        results[idx].rollbacks = node->rollbacks();

        spdlog::info("[p2p] node {:2d}  stepped={:4}  rollbacks={:4}  rollback_rate={:.1f}%",
                     id,
                     results[idx].stepped,
                     results[idx].rollbacks,
                     100.0 * static_cast<double>(results[idx].rollbacks) /
                         static_cast<double>(results[idx].stepped + 1));
    };

    std::vector<std::thread> threads;
    threads.reserve(NUM_PEERS);
    for (int i = 0; i < NUM_PEERS; ++i)
        threads.emplace_back(run_node, i);
    for (auto& t : threads) t.join();

    // ── assertions ────────────────────────────────────────────────────────────

    uint64_t min_stepped = std::numeric_limits<uint64_t>::max();
    uint64_t max_stepped = 0;

    for (int i = 0; i < NUM_PEERS; ++i) {
        REQUIRE(results[i].stepped > 0);
        min_stepped = std::min(min_stepped, results[i].stepped);
        max_stepped = std::max(max_stepped, results[i].stepped);
    }

    double sync_ratio =
        static_cast<double>(min_stepped) / static_cast<double>(max_stepped);
    REQUIRE(sync_ratio > 0.80);

    cleanup_files();
}
