#include "client.h"
#include "server.h"
#include <random.h>
#include <exception.h>

#include <cstring>
#include <stdexcept>
#include <iostream>
#include <thread>
#include <chrono>

namespace paritypp {

client::client(size_t data_blocks, size_t parity_blocks,
               const std::vector<node_info>& nodes,
               const credentials& creds)
    : k_(data_blocks), m_(parity_blocks), nodes_(nodes),
      creds_(creds), parity_engine_(data_blocks, parity_blocks) {

    if (nodes_.size() < k_ + m_) {
        std::cerr << "paritypp::client: only " << nodes_.size()
                  << " node(s) for " << k_ << "+" << m_
                  << " config; parity store/retrieve disabled, "
                  << "peer replication still available" << std::endl;
    }

    connections_.resize(nodes_.size());
}

client::~client() = default;

void client::connect_to_node(size_t node_index) {
    auto& nc = connections_[node_index];
    // Reset any previous broken connection
    nc.socket.reset();
    nc.connected = false;

    nc.socket = std::make_unique<netplus::quic>();
    nc.socket->connect(nodes_[node_index].address, nodes_[node_index].port, false);

    // Wait for handshake using event-driven wait (epoll/kqueue/IOCP/poll)
    for (int i = 0; i < 50; i++) {
        if (nc.socket->hasPendingWrite()) {
            if (!wait_.waitWrite(*nc.socket, 100)) continue;
        } else {
            if (!wait_.waitRead(*nc.socket, 100)) continue;
        }
        nc.socket->pumpNetwork(MSG_DONTWAIT);
        if (nc.socket->getHandshakeDone()) {
            nc.connected = true;
            // Authenticate if credentials provided
            if (!creds_.client_name.empty()) {
                if (!authenticate_node(node_index)) {
                    nc.socket.reset();
                    nc.connected = false;
                    throw std::runtime_error("Authentication failed on node " +
                                              nodes_[node_index].address);
                }
            }
            return;
        }
    }
    nc.socket.reset();
    nc.connected = false;
    throw std::runtime_error("Failed to connect to node " + nodes_[node_index].address);
}

bool client::authenticate_node(size_t node_index) {
    // Step 1: Send AUTH_REQUEST with client name
    auto req_payload = protocol::make_auth_request(creds_.client_name);
    auto response = send_recv(node_index, msg_type::AUTH_REQUEST, req_payload);

    if (response.size() < protocol::HEADER_SIZE) return false;

    msg_type resp_type;
    uint32_t resp_len;
    protocol::decode_header(response.data(), response.size(), resp_type, resp_len);

    if (resp_type != msg_type::AUTH_CHALLENGE) return false;

    // Step 2: Parse challenge nonce
    std::vector<uint8_t> nonce;
    if (!protocol::parse_auth_challenge(response.data() + protocol::HEADER_SIZE,
                                         resp_len, nonce)) {
        return false;
    }

    // Step 3: Compute HMAC-SHA256(key, nonce) and send AUTH_RESPONSE
    auto hmac = auth::compute_hmac(creds_.key, nonce);
    auto auth_resp = protocol::make_auth_response(hmac);
    auto final_response = send_recv(node_index, msg_type::AUTH_RESPONSE, auth_resp);

    if (final_response.size() < protocol::HEADER_SIZE) return false;

    protocol::decode_header(final_response.data(), final_response.size(),
                            resp_type, resp_len);

    return resp_type == msg_type::AUTH_OK;
}

void client::ensure_connected(size_t node_index) {
    if (connections_[node_index].connected && connections_[node_index].socket) {
        return;
    }
    // Reset broken state before reconnecting
    connections_[node_index].socket.reset();
    connections_[node_index].connected = false;
    connect_to_node(node_index);
}

std::vector<uint8_t> client::send_recv(size_t node_index,
                                         msg_type type,
                                         const std::vector<uint8_t>& payload) {
    ensure_connected(node_index);
    auto& conn = *connections_[node_index].socket;

    uint64_t stream = conn.openStream(true);
    auto msg = protocol::encode_message(type, payload);

    // Non-blocking send: wait for writable, then send
    for (int sw = 0; sw < 150; sw++) {
        if (wait_.waitWrite(conn, 100)) {
            conn.sendStreamData(stream, msg, true);
            break;
        }
        conn.pumpNetwork(MSG_DONTWAIT);
    }

    // Wait for response using event-driven waitRead (epoll/kqueue/IOCP/poll)
    std::vector<uint8_t> response;
    for (int attempt = 0; attempt < 150; attempt++) {
        if (!wait_.waitRead(conn, 100)) continue;
        conn.pumpNetwork(MSG_DONTWAIT);
        if (conn.hasStreamData(stream)) {
            // Read header
            uint8_t hdr[protocol::HEADER_SIZE];
            size_t r = conn.recvStreamData(stream, hdr, protocol::HEADER_SIZE);
            if (r < protocol::HEADER_SIZE) continue;

            msg_type resp_type;
            uint32_t resp_len;
            if (!protocol::decode_header(hdr, r, resp_type, resp_len)) continue;

            response.resize(protocol::HEADER_SIZE + resp_len);
            std::memcpy(response.data(), hdr, protocol::HEADER_SIZE);

            size_t total_read = 0;
            int body_attempts = 0;
            while (total_read < resp_len && body_attempts++ < 150) {
                if (!wait_.waitRead(conn, 100)) continue;
                conn.pumpNetwork(MSG_DONTWAIT);
                size_t rd = conn.recvStreamData(stream,
                                                 response.data() + protocol::HEADER_SIZE + total_read,
                                                 resp_len - total_read);
                total_read += rd;
            }
            break;
        }
    }
    conn.closeStream(stream);
    // No response after all attempts — mark connection as broken so
    // subsequent calls (e.g. get_cluster_status) fail fast instead of
    // retrying the expensive connect + send loop on a dead node.
    if (response.empty()) {
        connections_[node_index].connected = false;
        connections_[node_index].socket.reset();
    }
    return response;
}

bool client::store_block_on_node(size_t node_index, uint64_t group_id,
                                   uint32_t block_index,
                                   const uint8_t* data, size_t len) {
    if (static_cast<int>(node_index) == local_node_index_ && local_store_) {
        return local_store_->store(group_id, block_index, data, len);
    }
    auto payload = protocol::make_store_block(group_id, block_index, data, len);
    auto response = send_recv(node_index, msg_type::STORE_BLOCK, payload);

    if (response.size() >= protocol::HEADER_SIZE) {
        msg_type type;
        uint32_t resp_len;
        protocol::decode_header(response.data(), response.size(), type, resp_len);
        return type == msg_type::OK;
    }
    return false;
}

bool client::fetch_block_from_node(size_t node_index, uint64_t group_id,
                                     uint32_t block_index,
                                     std::vector<uint8_t>& out) {
    if (static_cast<int>(node_index) == local_node_index_ && local_store_) {
        return local_store_->fetch(group_id, block_index, out);
    }
    auto payload = protocol::make_fetch_block(group_id, block_index);
    auto response = send_recv(node_index, msg_type::FETCH_BLOCK, payload);

    if (response.size() >= protocol::HEADER_SIZE) {
        msg_type type;
        uint32_t resp_len;
        protocol::decode_header(response.data(), response.size(), type, resp_len);
        if (type == msg_type::BLOCK_DATA && resp_len >= 12) {
            const uint8_t* data;
            size_t data_len;
            uint64_t gid;
            uint32_t bidx;
            if (protocol::parse_block_data(response.data() + protocol::HEADER_SIZE,
                                            resp_len, gid, bidx, data, data_len)) {
                out.assign(data, data + data_len);
                return true;
            }
        }
    }
    return false;
}

uint64_t client::generate_group_id() {
    // Use random bytes to generate a unique group ID
    uint64_t id = 0;
    uint8_t buf[8];
    // Simple time-based + random fallback
    auto now = std::chrono::steady_clock::now().time_since_epoch();
    id = static_cast<uint64_t>(
        std::chrono::duration_cast<std::chrono::nanoseconds>(now).count());
    return id;
}

uint64_t client::store(const uint8_t* data, size_t data_len) {
    if (nodes_.size() < k_ + m_) {
        throw std::runtime_error("Not enough nodes for parity store ("
                                 + std::to_string(nodes_.size()) + " < "
                                 + std::to_string(k_ + m_) + ")");
    }

    uint64_t group_id = generate_group_id();

    // Split data into k blocks
    auto data_blocks = parity::split(data, data_len, k_);

    // Compute m parity blocks
    auto parity_blocks = parity_engine_.encode(data_blocks);

    // Distribute: data block i -> node i, parity block j -> node k+j
    // In degraded mode, tolerate up to m node failures
    size_t failed = 0;
    for (size_t i = 0; i < k_; i++) {
        try {
            if (!store_block_on_node(i, group_id, static_cast<uint32_t>(i),
                                      data_blocks[i].data(), data_blocks[i].size())) {
                ++failed;
                std::cerr << "store: data block " << i << " failed (store rejected)" << std::endl;
            }
        } catch (const std::exception& e) {
            ++failed;
            connections_[i].socket.reset();
            connections_[i].connected = false;
            std::cerr << "store: data block " << i << " failed: " << e.what() << std::endl;
        }
    }
    for (size_t j = 0; j < m_; j++) {
        try {
            if (!store_block_on_node(k_ + j, group_id, static_cast<uint32_t>(k_ + j),
                                      parity_blocks[j].data(), parity_blocks[j].size())) {
                ++failed;
                std::cerr << "store: parity block " << j << " failed (store rejected)" << std::endl;
            }
        } catch (const std::exception& e) {
            ++failed;
            connections_[k_ + j].socket.reset();
            connections_[k_ + j].connected = false;
            std::cerr << "store: parity block " << j << " failed: " << e.what() << std::endl;
        }
    }

    if (failed > m_) {
        throw std::runtime_error("Too many node failures during store: "
                                 + std::to_string(failed) + " failed, max tolerable: "
                                 + std::to_string(m_));
    }

    return group_id;
}

uint64_t client::store(const std::vector<uint8_t>& data) {
    return store(data.data(), data.size());
}

void client::store(uint64_t group_id, const uint8_t* data, size_t data_len) {
    if (nodes_.size() < k_ + m_) {
        throw std::runtime_error("Not enough nodes for parity store ("
                                 + std::to_string(nodes_.size()) + " < "
                                 + std::to_string(k_ + m_) + ")");
    }

    // Prepend 8-byte length header so retrieve() can strip padding
    std::vector<uint8_t> framed(8 + data_len);
    protocol::encode_u64(framed.data(), static_cast<uint64_t>(data_len));
    if (data_len > 0)
        std::memcpy(framed.data() + 8, data, data_len);

    auto data_blocks = parity::split(framed.data(), framed.size(), k_);
    auto parity_blocks = parity_engine_.encode(data_blocks);

    size_t failed = 0;
    for (size_t i = 0; i < k_; i++) {
        try {
            if (!store_block_on_node(i, group_id, static_cast<uint32_t>(i),
                                      data_blocks[i].data(), data_blocks[i].size())) {
                ++failed;
                std::cerr << "store(gid): data block " << i << " failed (store rejected)" << std::endl;
            }
        } catch (const std::exception& e) {
            ++failed;
            connections_[i].socket.reset();
            connections_[i].connected = false;
            std::cerr << "store(gid): data block " << i << " failed: " << e.what() << std::endl;
        }
    }
    for (size_t j = 0; j < m_; j++) {
        try {
            if (!store_block_on_node(k_ + j, group_id, static_cast<uint32_t>(k_ + j),
                                      parity_blocks[j].data(), parity_blocks[j].size())) {
                ++failed;
                std::cerr << "store(gid): parity block " << j << " failed (store rejected)" << std::endl;
            }
        } catch (const std::exception& e) {
            ++failed;
            connections_[k_ + j].socket.reset();
            connections_[k_ + j].connected = false;
            std::cerr << "store(gid): parity block " << j << " failed: " << e.what() << std::endl;
        }
    }

    if (failed > m_) {
        throw std::runtime_error("Too many node failures during store(gid): "
                                 + std::to_string(failed) + " failed, max tolerable: "
                                 + std::to_string(m_));
    }
}

void client::store(uint64_t group_id, const std::vector<uint8_t>& data) {
    store(group_id, data.data(), data.size());
}

std::vector<uint8_t> client::retrieve(uint64_t group_id, size_t original_size) {
    size_t n = k_ + m_;

    // Try to fetch all blocks, record which ones we got.
    // Gracefully skip unreachable nodes — Reed-Solomon can reconstruct
    // from any k out of k+m blocks.
    std::vector<std::vector<uint8_t>> fetched_blocks;
    std::vector<size_t> fetched_indices;

    for (size_t i = 0; i < n && fetched_blocks.size() < k_; i++) {
        try {
            std::vector<uint8_t> block;
            if (fetch_block_from_node(i, group_id, static_cast<uint32_t>(i), block)) {
                fetched_blocks.push_back(std::move(block));
                fetched_indices.push_back(i);
            }
        } catch (const std::exception& e) {
            connections_[i].socket.reset();
            connections_[i].connected = false;
            std::cerr << "retrieve: node " << i << " unreachable: "
                      << e.what() << std::endl;
        }
    }

    if (fetched_blocks.size() < k_) {
        throw std::runtime_error("Not enough blocks available to reconstruct data. "
                                 "Got " + std::to_string(fetched_blocks.size()) +
                                 ", need " + std::to_string(k_));
    }

    // Check if we got all data blocks directly
    bool all_data_present = true;
    for (size_t i = 0; i < k_; i++) {
        bool found = false;
        for (size_t j = 0; j < fetched_indices.size(); j++) {
            if (fetched_indices[j] == i) {
                found = true;
                break;
            }
        }
        if (!found) {
            all_data_present = false;
            break;
        }
    }

    std::vector<std::vector<uint8_t>> data_blocks;
    if (all_data_present) {
        // Reorder data blocks by index
        data_blocks.resize(k_);
        for (size_t j = 0; j < fetched_indices.size(); j++) {
            if (fetched_indices[j] < k_) {
                data_blocks[fetched_indices[j]] = std::move(fetched_blocks[j]);
            }
        }
    } else {
        // Need Reed-Solomon decoding
        size_t block_size = fetched_blocks[0].size();
        data_blocks = parity_engine_.decode(fetched_blocks, fetched_indices, block_size);
    }

    return parity::join(data_blocks, original_size);
}

std::vector<uint8_t> client::retrieve(uint64_t group_id) {
    // Retrieve framed data (8-byte length header + payload)
    // We don't know the original size, so pass 0 to get all data
    size_t n = k_ + m_;

    std::vector<std::vector<uint8_t>> fetched_blocks;
    std::vector<size_t> fetched_indices;

    for (size_t i = 0; i < n && fetched_blocks.size() < k_; i++) {
        try {
            std::vector<uint8_t> block;
            if (fetch_block_from_node(i, group_id, static_cast<uint32_t>(i), block)) {
                fetched_blocks.push_back(std::move(block));
                fetched_indices.push_back(i);
            }
        } catch (const std::exception& e) {
            connections_[i].socket.reset();
            connections_[i].connected = false;
            std::cerr << "retrieve: node " << i << " unreachable: "
                      << e.what() << std::endl;
        }
    }

    if (fetched_blocks.size() < k_) {
        throw std::runtime_error("Not enough blocks available to reconstruct data. "
                                 "Got " + std::to_string(fetched_blocks.size()) +
                                 ", need " + std::to_string(k_));
    }

    bool all_data_present = true;
    for (size_t i = 0; i < k_; i++) {
        bool found = false;
        for (size_t j = 0; j < fetched_indices.size(); j++) {
            if (fetched_indices[j] == i) { found = true; break; }
        }
        if (!found) { all_data_present = false; break; }
    }

    std::vector<std::vector<uint8_t>> data_blocks;
    if (all_data_present) {
        data_blocks.resize(k_);
        for (size_t j = 0; j < fetched_indices.size(); j++) {
            if (fetched_indices[j] < k_) {
                data_blocks[fetched_indices[j]] = std::move(fetched_blocks[j]);
            }
        }
    } else {
        size_t block_size = fetched_blocks[0].size();
        data_blocks = parity_engine_.decode(fetched_blocks, fetched_indices, block_size);
    }

    // Join all blocks, then strip the 8-byte length header
    auto framed = parity::join(data_blocks, 0);
    if (framed.size() < 8) {
        throw std::runtime_error("retrieve: reconstructed data too small for length header");
    }
    uint64_t original_size = protocol::decode_u64(framed.data());
    if (original_size > framed.size() - 8) {
        throw std::runtime_error("retrieve: invalid length header ("
                                 + std::to_string(original_size) + " > "
                                 + std::to_string(framed.size() - 8) + ")");
    }
    return std::vector<uint8_t>(framed.begin() + 8, framed.begin() + 8 + original_size);
}

void client::remove(uint64_t group_id) {
    std::vector<uint8_t> payload(8);
    protocol::encode_u64(payload.data(), group_id);

    for (size_t i = 0; i < nodes_.size(); i++) {
        try {
            send_recv(i, msg_type::DELETE_GROUP, payload);
        } catch (...) {
            // Best-effort deletion
        }
    }
}

std::vector<std::pair<size_t, std::vector<uint32_t>>> client::list(uint64_t group_id) {
    std::vector<std::pair<size_t, std::vector<uint32_t>>> result;
    std::vector<uint8_t> payload(8);
    protocol::encode_u64(payload.data(), group_id);

    for (size_t i = 0; i < nodes_.size(); i++) {
        try {
            auto response = send_recv(i, msg_type::LIST_BLOCKS, payload);
            if (response.size() >= protocol::HEADER_SIZE) {
                msg_type type;
                uint32_t resp_len;
                protocol::decode_header(response.data(), response.size(), type, resp_len);
                if (type == msg_type::BLOCK_LIST && resp_len >= 12) {
                    const uint8_t* rp = response.data() + protocol::HEADER_SIZE;
                    uint32_t count = protocol::decode_u32(rp + 8);
                    std::vector<uint32_t> indices;
                    for (uint32_t j = 0; j < count && (12 + j * 4) < resp_len; j++) {
                        indices.push_back(protocol::decode_u32(rp + 12 + j * 4));
                    }
                    result.emplace_back(i, std::move(indices));
                }
            }
        } catch (...) {}
    }
    return result;
}

size_t client::replicate_to_peers(uint64_t group_id, uint32_t block_index,
                                 const uint8_t* data, size_t len) {
    static constexpr int MAX_RETRIES = 2;
    static constexpr int BASE_DELAY_MS = 100;
    size_t success_count = 0;

    for (size_t i = 0; i < nodes_.size(); i++) {
        bool ok = false;
        for (int attempt = 0; attempt < MAX_RETRIES && !ok; attempt++) {
            try {
                if (store_block_on_node(i, group_id, block_index, data, len)) {
                    ok = true;
                    success_count++;
                } else {
                    std::cerr << "replicate_to_peers: node " << i
                              << " store rejected (attempt " << (attempt + 1)
                              << "/" << MAX_RETRIES << ")" << std::endl;
                    connections_[i].socket.reset();
                    connections_[i].connected = false;
                    if (attempt + 1 < MAX_RETRIES) {
                        int delay = BASE_DELAY_MS * (1 << attempt);
                        std::this_thread::sleep_for(std::chrono::milliseconds(delay));
                    }
                }
            } catch (const netplus::NetException &e) {
                connections_[i].socket.reset();
                connections_[i].connected = false;
                if (attempt + 1 < MAX_RETRIES) {
                    int delay = BASE_DELAY_MS * (1 << attempt);
                    std::cerr << "replicate_to_peers: node " << i
                              << " failed (attempt " << (attempt + 1)
                              << "): " << e.what()
                              << ", retrying in " << delay << "ms" << std::endl;
                    std::this_thread::sleep_for(std::chrono::milliseconds(delay));
                } else {
                    std::cerr << "replicate_to_peers: node " << i
                              << " failed after " << MAX_RETRIES
                              << " attempts: " << e.what() << std::endl;
                }
            } catch (const std::exception &e) {
                connections_[i].socket.reset();
                connections_[i].connected = false;
                if (attempt + 1 < MAX_RETRIES) {
                    int delay = BASE_DELAY_MS * (1 << attempt);
                    std::cerr << "replicate_to_peers: node " << i
                              << " failed (attempt " << (attempt + 1)
                              << "): " << e.what()
                              << ", retrying in " << delay << "ms" << std::endl;
                    std::this_thread::sleep_for(std::chrono::milliseconds(delay));
                } else {
                    std::cerr << "replicate_to_peers: node " << i
                              << " failed after " << MAX_RETRIES
                              << " attempts: " << e.what() << std::endl;
                }
            }
        }
    }
    return success_count;
}

bool client::fetch_from_peers(uint64_t group_id, uint32_t block_index,
                               std::vector<uint8_t>& out) {
    static constexpr int MAX_RETRIES = 2;
    static constexpr int BASE_DELAY_MS = 100;

    for (size_t i = 0; i < nodes_.size(); i++) {
        for (int attempt = 0; attempt < MAX_RETRIES; attempt++) {
            try {
                if (fetch_block_from_node(i, group_id, block_index, out) && !out.empty())
                    return true;
                break; // fetch succeeded but block not found on this node, try next
            } catch (const netplus::NetException &e) {
                connections_[i].socket.reset();
                connections_[i].connected = false;
                if (attempt + 1 < MAX_RETRIES) {
                    int delay = BASE_DELAY_MS * (1 << attempt);
                    std::cerr << "fetch_from_peers: node " << i
                              << " failed (attempt " << (attempt + 1)
                              << "): " << e.what()
                              << ", retrying in " << delay << "ms" << std::endl;
                    std::this_thread::sleep_for(std::chrono::milliseconds(delay));
                } else {
                    std::cerr << "fetch_from_peers: node " << i
                              << " failed after " << MAX_RETRIES
                              << " attempts: " << e.what() << std::endl;
                }
            } catch (const std::exception &e) {
                connections_[i].socket.reset();
                connections_[i].connected = false;
                if (attempt + 1 < MAX_RETRIES) {
                    int delay = BASE_DELAY_MS * (1 << attempt);
                    std::cerr << "fetch_from_peers: node " << i
                              << " failed (attempt " << (attempt + 1)
                              << "): " << e.what()
                              << ", retrying in " << delay << "ms" << std::endl;
                    std::this_thread::sleep_for(std::chrono::milliseconds(delay));
                } else {
                    std::cerr << "fetch_from_peers: node " << i
                              << " failed after " << MAX_RETRIES
                              << " attempts: " << e.what() << std::endl;
                }
            }
        }
    }
    return false;
}

bool client::get_node_status(size_t node_index, status_info& info) {
    if (node_index >= nodes_.size()) return false;
    try {
        auto resp = send_recv(node_index, msg_type::GET_STATUS, {});
        if (resp.size() < protocol::HEADER_SIZE) return false;

        msg_type rtype;
        uint32_t rlen;
        if (!protocol::decode_header(resp.data(), resp.size(), rtype, rlen))
            return false;

        if (rtype != msg_type::STATUS_RESP)
            return false;

        return protocol::parse_status_resp(
            resp.data() + protocol::HEADER_SIZE, rlen, info);
    } catch (...) {
        return false;
    }
}

bool client::list_groups_on_node(size_t node_index, std::vector<uint64_t>& groups) {
    if (node_index >= nodes_.size()) return false;
    if (static_cast<int>(node_index) == local_node_index_ && local_store_) {
        groups = local_store_->list_groups();
        return true;
    }
    try {
        auto resp = send_recv(node_index, msg_type::LIST_GROUPS, {});
        if (resp.size() < protocol::HEADER_SIZE) return false;

        msg_type rtype;
        uint32_t rlen;
        if (!protocol::decode_header(resp.data(), resp.size(), rtype, rlen))
            return false;

        if (rtype != msg_type::GROUP_LIST)
            return false;

        return protocol::parse_group_list(
            resp.data() + protocol::HEADER_SIZE, rlen, groups);
    } catch (...) {
        return false;
    }
}

client::cluster_health client::get_cluster_status() {
    cluster_health health;
    health.nodes_total = static_cast<uint32_t>(nodes_.size());
    health.nodes_online = 0;
    health.overall = node_status::OK;

    for (size_t i = 0; i < nodes_.size(); i++) {
        status_info info;
        if (get_node_status(i, info)) {
            health.nodes_online++;
            health.node_statuses.emplace_back(i, info);
            // If any node reports degraded, note it
            if (info.status == node_status::DEGRADED ||
                info.status == node_status::REBUILD) {
                health.overall = info.status;
            }
        }
    }

    // Determine overall status based on reachability
    if (health.nodes_online == 0) {
        health.overall = node_status::DEGRADED;
    } else if (health.nodes_online < health.nodes_total) {
        // Some nodes unreachable — still degraded even if individual nodes report OK
        if (health.overall == node_status::OK)
            health.overall = node_status::DEGRADED;
    }

    return health;
}

void client::set_local_node(size_t node_index, std::shared_ptr<block_store> store) {
    local_node_index_ = static_cast<int>(node_index);
    local_store_ = std::move(store);
}

} // namespace paritypp
