#include "PeerLink.hpp"

#include <cstring>
#include <spdlog/spdlog.h>

namespace tw::p2p {

PeerLink::PeerLink(uint32_t self_id, uint32_t remote_id,
                   tw::net::quicr::QuicrConnection* connection)
    : m_self_id(self_id)
    , m_remote_id(remote_id)
    , m_connection(connection)
    , m_recv_buffer(64 * 1024)
{}

// ── outbound ──────────────────────────────────────────────────────────────────

void PeerLink::send_hello(const mmo::peer::PeerHello& msg) {
    send(MsgType::Hello, msg, /*reliable=*/true);
}

void PeerLink::send_action(const mmo::peer::PeerAction& msg) {
    send(MsgType::Action, msg, /*reliable=*/false);
}

void PeerLink::send_bye(const mmo::peer::PeerBye& msg) {
    send(MsgType::Bye, msg, /*reliable=*/true);
}

void PeerLink::send(MsgType type, const google::protobuf::MessageLite& msg, bool reliable) {
    std::string payload = msg.SerializeAsString();

    std::vector<std::byte> frame(sizeof(uint32_t) + payload.size());
    uint32_t tag = static_cast<uint32_t>(type);
    std::memcpy(frame.data(), &tag, sizeof(tag));
    std::memcpy(frame.data() + sizeof(tag), payload.data(), payload.size());

    auto r = m_connection->send_message(std::span(frame), reliable);
    if (!r) {
        spdlog::warn("PeerLink[{}->{}]: send failed", m_self_id, m_remote_id);
    }
}

// ── inbound ───────────────────────────────────────────────────────────────────

void PeerLink::poll() {
    while (true) {
        auto r = m_connection->read_into(std::span(m_recv_buffer));
        if (!r || *r == 0) break;
        dispatch(std::span<const std::byte>(m_recv_buffer.data(), *r));
    }
}

void PeerLink::dispatch(std::span<const std::byte> frame) {
    if (frame.size() < sizeof(uint32_t)) return;

    uint32_t tag;
    std::memcpy(&tag, frame.data(), sizeof(tag));
    auto payload = frame.subspan(sizeof(tag));
    spdlog::info("Received into a link");

    switch (static_cast<MsgType>(tag)) {
        case MsgType::Hello: {
            mmo::peer::PeerHello msg;
            if (msg.ParseFromArray(payload.data(), static_cast<int>(payload.size())) && m_hello_handler)
                m_hello_handler(msg);
            break;
        }
        case MsgType::Action: {
            mmo::peer::PeerAction msg;
            if (msg.ParseFromArray(payload.data(), static_cast<int>(payload.size())) && m_action_handler)
                m_action_handler(msg);
            break;
        }
        case MsgType::Bye: {
            mmo::peer::PeerBye msg;
            if (msg.ParseFromArray(payload.data(), static_cast<int>(payload.size())) && m_bye_handler)
                m_bye_handler(msg);
            break;
        }
        default:
            spdlog::warn("PeerLink[{}]: unknown message type {}", m_self_id, tag);
    }
}

}
