/*
 * test_frame_codec — unit test for FrameCodec::feed / ::emit.
 *
 * No third-party dependencies; runs under both the skeleton and dev presets.
 * Tests: empty payload, single byte, 64 KiB, exact max-size, oversized (should reject),
 * split delivery (bytes fed one-at-a-time), and batched multi-frame delivery.
 */
#include <cstdio>
#include <cstring>
#include <vector>

#include "protocol/protocol.h"

using namespace voicecat::protocol;

static int g_failures = 0;

#define CHECK(cond)                                                        \
    do {                                                                   \
        if (!(cond)) {                                                     \
            std::printf("FAIL [%s:%d]: %s\n", __FILE__, __LINE__, #cond); \
            ++g_failures;                                                  \
        }                                                                  \
    } while (0)

// Round-trip a single payload through emit → feed.
static void round_trip(const std::vector<uint8_t>& payload, const char* label) {
    std::vector<uint8_t> wire;
    FrameCodec::emit(payload, wire);

    FrameCodec codec;
    std::vector<std::vector<uint8_t>> frames;
    bool ok = codec.feed(wire.data(), wire.size(), frames);
    CHECK(ok);
    CHECK(frames.size() == 1);
    if (!frames.empty()) {
        CHECK(frames[0] == payload);
    }
    (void)label;
}

int main() {
    // --- Empty payload ---
    round_trip({}, "empty");

    // --- Single byte ---
    round_trip({0xAB}, "1 byte");

    // --- 64 KiB payload ---
    {
        std::vector<uint8_t> big(64 * 1024);
        for (size_t i = 0; i < big.size(); ++i) big[i] = static_cast<uint8_t>(i & 0xFF);
        round_trip(big, "64 KiB");
    }

    // --- Exactly kMaxFrameBytes ---
    {
        std::vector<uint8_t> max_payload(kMaxFrameBytes, 0x5A);
        std::vector<uint8_t> wire;
        FrameCodec::emit(max_payload, wire);

        FrameCodec codec;
        std::vector<std::vector<uint8_t>> frames;
        bool ok = codec.feed(wire.data(), wire.size(), frames);
        CHECK(ok);
        CHECK(frames.size() == 1);
        if (!frames.empty()) CHECK(frames[0] == max_payload);
    }

    // --- One byte over kMaxFrameBytes — must be rejected ---
    {
        // Craft a fake header with length = kMaxFrameBytes + 1.
        uint32_t bad_len = kMaxFrameBytes + 1;
        uint8_t header[4] = {
            static_cast<uint8_t>((bad_len >> 24) & 0xFF),
            static_cast<uint8_t>((bad_len >> 16) & 0xFF),
            static_cast<uint8_t>((bad_len >>  8) & 0xFF),
            static_cast<uint8_t>( bad_len         & 0xFF),
        };
        FrameCodec codec;
        std::vector<std::vector<uint8_t>> frames;
        bool ok = codec.feed(header, 4, frames);
        CHECK(!ok);           // must return false
        CHECK(frames.empty());
    }

    // --- Byte-at-a-time delivery (reassembly) ---
    {
        std::vector<uint8_t> payload = {1, 2, 3, 4, 5};
        std::vector<uint8_t> wire;
        FrameCodec::emit(payload, wire);

        FrameCodec codec;
        std::vector<std::vector<uint8_t>> frames;
        bool ok = true;
        for (uint8_t b : wire) {
            ok = codec.feed(&b, 1, frames);
            if (!ok) break;
        }
        CHECK(ok);
        CHECK(frames.size() == 1);
        if (!frames.empty()) CHECK(frames[0] == payload);
    }

    // --- Multiple frames in a single feed() call ---
    {
        std::vector<uint8_t> p1 = {0x01, 0x02};
        std::vector<uint8_t> p2 = {0xAA, 0xBB, 0xCC};
        std::vector<uint8_t> wire;
        FrameCodec::emit(p1, wire);
        FrameCodec::emit(p2, wire);

        FrameCodec codec;
        std::vector<std::vector<uint8_t>> frames;
        bool ok = codec.feed(wire.data(), wire.size(), frames);
        CHECK(ok);
        CHECK(frames.size() == 2);
        if (frames.size() == 2) {
            CHECK(frames[0] == p1);
            CHECK(frames[1] == p2);
        }
    }

    // --- pending_bytes() reflects partial state ---
    {
        std::vector<uint8_t> payload = {0xFF};
        std::vector<uint8_t> wire;
        FrameCodec::emit(payload, wire);  // 5 bytes total (4 hdr + 1)

        FrameCodec codec;
        std::vector<std::vector<uint8_t>> frames;

        // Feed only the header.
        codec.feed(wire.data(), 4, frames);
        CHECK(frames.empty());
        CHECK(codec.pending_bytes() == 4);

        // Feed the body.
        codec.feed(wire.data() + 4, 1, frames);
        CHECK(frames.size() == 1);
        CHECK(codec.pending_bytes() == 0);
    }

    if (g_failures == 0) {
        std::printf("frame_codec: all checks passed\n");
        return 0;
    }
    std::printf("frame_codec: %d failure(s)\n", g_failures);
    return 1;
}
