// Standalone tests for reasampler::wire — no REAPER, no framework. The ONE
// length-prefixed ext-state wire codec (Q-W1, T2-01b) behind provenance /
// assignment_request / sample_usage / bank_sync. The consumers' own suites
// prove their record grammars round-trip; this suite pins the CODEC contract —
// byte-exact encode, the full hardening (length caps, overflow guards,
// subtraction-first bounds), and the fixed fieldInt range rejection.
//
// NOTE: wire::Cursor BORROWS its input string, so every helper takes a named /
// reference-bound std::string — the Cursor never outlives its buffer.

#include "../src/core/wire/wire.h"
#include "../src/core/wire/bytes.h"  // putLE / ByteReader — the ONE LE byte codec (Q-W2v, T4-20)

#include <cstdint>
#include <cstdio>
#include <string>
#include <vector>

using namespace reasampler;
using namespace reasampler::wire;

static int g_fail = 0;
#define CHECK(cond) do { if(!(cond)) { \
    std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)

// One length-prefixed field around `v` — the writer-side convention.
static std::string enc(const std::string& v) {
    std::string out;
    wire::putField(out, v);
    return out;
}

// Single-field decode helpers (Cursor + buffer share the call's lifetime).
static bool fieldFrom(const std::string& s, std::string& out) {
    wire::Cursor c(s);
    return c.field(out);
}
static bool i64From(const std::string& s, std::int64_t& out) {
    wire::Cursor c(s);
    return c.fieldInt64(out);
}
static bool intFrom(const std::string& s, int& out) {
    wire::Cursor c(s);
    return c.fieldInt(out);
}
static bool sizeFrom(const std::string& s, std::size_t& out) {
    wire::Cursor c(s);
    return c.fieldSizeT(out);
}
static bool dblFrom(const std::string& s, double& out) {
    wire::Cursor c(s);
    return c.fieldDouble(out);
}

// --- putField: byte-exact encode ----------------------------------------------

static void testPutFieldExactBytes() {
    std::string out;
    wire::putField(out, "abc");
    CHECK(out == "3:abc");
    wire::putField(out, "");          // empty field is legal: "0:"
    CHECK(out == "3:abc0:");
    wire::putField(out, "a:b");       // ':' inside a value cannot shift the parse
    CHECK(out == "3:abc0:3:a:b");
}

// --- Cursor: round-trip + literal ---------------------------------------------

static void testFieldRoundTripIncludingSeparators() {
    std::string out = "magic";
    wire::putField(out, "12:34");     // digits + colons in the value
    wire::putField(out, "");
    wire::putField(out, "tail");
    wire::Cursor c(out);
    std::string a, b, t;
    CHECK(c.literal("magic"));
    CHECK(c.field(a) && a == "12:34");
    CHECK(c.field(b) && b.empty());
    CHECK(c.field(t) && t == "tail");
    CHECK(c.ok() && c.atEnd());
}

static void testLiteralMismatchFails() {
    const std::string good = "rsprov1x";
    const std::string wrong = "rsprov0x";
    const std::string truncated = "rspro";
    { wire::Cursor c(good);      CHECK(c.literal("rsprov1")); }
    { wire::Cursor c(wrong);     CHECK(!c.literal("rsprov1")); CHECK(!c.ok()); }
    { wire::Cursor c(truncated); CHECK(!c.literal("rsprov1")); }
}

// --- Cursor: field hardening ---------------------------------------------------

static void testFieldRejectsMalformedLengths() {
    std::string f;
    CHECK(!fieldFrom("abc", f));    // no colon
    CHECK(!fieldFrom(":x", f));     // empty length
    CHECK(!fieldFrom("2x:ab", f));  // non-digit length
    CHECK(!fieldFrom("9:ab", f));   // runs past end
    // A 200-digit length cannot accumulate past SIZE_MAX (digit-run cap).
    CHECK(!fieldFrom(std::string(200, '9') + ":x", f));
    // Exactly-20-digit values: SIZE_MAX itself passes the accumulate but fails the
    // bounds check; one past SIZE_MAX trips the overflow guard.
    CHECK(!fieldFrom("18446744073709551615:x", f));
    CHECK(!fieldFrom("18446744073709551616:x", f));
}

static void testFailureLatchesOk() {
    // After one failed read every subsequent read fails too — the caller may
    // check ok() once at the end (the "never a partial value" discipline).
    const std::string s = "3:abc";
    wire::Cursor c(s);
    std::string f;
    CHECK(!c.literal("nope"));
    CHECK(!c.field(f));
    CHECK(!c.ok());
}

// --- Cursor: fieldInt64 / fieldInt --------------------------------------------

static void testFieldInt64AcceptsAndRejects() {
    std::int64_t v = 0;
    CHECK(i64From(enc("12345"), v)); CHECK(v == 12345);
    CHECK(i64From(enc("-42"), v));   CHECK(v == -42);
    CHECK(i64From(enc("9223372036854775807"), v));  // INT64_MAX
    CHECK(v == 9223372036854775807LL);
    CHECK(!i64From(enc("9223372036854775808"), v));   // overflow
    CHECK(!i64From(enc("12345678901234567890"), v));  // 20-digit cap
    CHECK(!i64From(enc("-"), v));                     // bare sign
    CHECK(!i64From(enc("1a"), v));                    // non-digit
    CHECK(!i64From(enc(""), v));                      // empty
}

static void testFieldIntRejectsOutOfIntRange() {
    // The fixed form of the old provenance strtol TODO: an out-of-int-range field
    // FAILS the parse instead of silently narrowing.
    int v = 0;
    CHECK(intFrom(enc("2147483647"), v));  CHECK(v == 2147483647);
    CHECK(intFrom(enc("-2147483648"), v)); CHECK(v == -2147483647 - 1);
    CHECK(!intFrom(enc("2147483648"), v));
    CHECK(!intFrom(enc("3000000000"), v));
}

// --- Cursor: fieldSizeT / fieldDouble ------------------------------------------

static void testFieldSizeT() {
    std::size_t v = 1;
    CHECK(sizeFrom(enc("0"), v));    CHECK(v == 0);
    CHECK(sizeFrom(enc("4096"), v)); CHECK(v == 4096);
    CHECK(!sizeFrom(enc("-1"), v));                     // sign = non-digit
    CHECK(!sizeFrom(enc(std::string(21, '9')), v));     // 21-digit cap
    CHECK(!sizeFrom(enc("18446744073709551616"), v));   // overflow guard
}

static void testFieldDoubleRoundTrip() {
    char buf[32];
    std::snprintf(buf, sizeof(buf), "%.17g", 3141.592653589793);
    double v = 0;
    CHECK(dblFrom(enc(buf), v));
    CHECK(v == 3141.592653589793);
    CHECK(!dblFrom(enc("1.5x"), v));  // trailing bytes
}

// --- parseUnsignedDecimal (the bank_sync generation core) -----------------------

static void testParseUnsignedDecimal() {
    std::int64_t v = 0;
    CHECK(wire::parseUnsignedDecimal("0", v) && v == 0);
    CHECK(wire::parseUnsignedDecimal("1721947293", v) && v == 1721947293);
    CHECK(wire::parseUnsignedDecimal("9223372036854775807", v) && v == 9223372036854775807LL);
    CHECK(!wire::parseUnsignedDecimal("", v));
    CHECK(!wire::parseUnsignedDecimal("+5", v));    // sign rejected (non-digit)
    CHECK(!wire::parseUnsignedDecimal("-5", v));
    CHECK(!wire::parseUnsignedDecimal("12a", v));
    CHECK(!wire::parseUnsignedDecimal("9223372036854775808", v));  // overflow
    CHECK(!wire::parseUnsignedDecimal(std::string(40, '9'), v));   // long run cannot wrap
}

// --- core/wire/bytes.h — the LE byte codec (Q-W2v, T4-20) --------------------

// putLE emits exactly the bytes the retired hand-rolled putU32le/putU64le emitted
// (LSB first, fixed width) — the FROZEN wire byte order.
static void testPutLEExactBytes() {
    std::vector<std::uint8_t> out;
    wire::putLE(out, static_cast<std::uint32_t>(0x0403'0201u));
    CHECK(out.size() == 4);
    CHECK(out[0] == 0x01 && out[1] == 0x02 && out[2] == 0x03 && out[3] == 0x04);
    out.clear();
    wire::putLE(out, static_cast<std::uint64_t>(0x0807'0605'0403'0201ull));
    CHECK(out.size() == 8);
    CHECK(out[0] == 0x01 && out[7] == 0x08);
}

// ByteReader round-trips putLE output, and the double bit-cast is lossless.
static void testByteReaderRoundTrip() {
    std::vector<std::uint8_t> out;
    wire::putLE(out, static_cast<std::uint32_t>(7));
    wire::putLE(out, static_cast<std::uint64_t>(wire::doubleToBits(-2.5)));
    wire::putLE(out, static_cast<std::uint8_t>(0xAB));
    wire::putLE(out,
                static_cast<std::uint64_t>(static_cast<std::int64_t>(-42)));  // i64 image
    wire::ByteReader r(out);
    CHECK(r.peekU32() == 7);
    CHECK(r.u32() == 7);
    CHECK(wire::bitsToDouble(r.u64()) == -2.5);
    CHECK(r.u8() == 0xAB);
    CHECK(r.i64() == -42);
    CHECK(r.ok);
}

// Truncation latches ok=false and every subsequent read yields zero/empty —
// the partial-parse contract every codec consumer leans on.
static void testByteReaderLatchesOnTruncation() {
    std::vector<std::uint8_t> out;
    wire::putLE(out, static_cast<std::uint32_t>(9));
    out.pop_back();  // truncate mid-u32
    wire::ByteReader r(out);
    CHECK(r.u32() == 0);
    CHECK(!r.ok);
    CHECK(r.u8() == 0);         // latched: even an in-bounds width now fails
    CHECK(r.str(1).empty());
    CHECK(r.peekU32() == 0);
}

int main() {
    testPutLEExactBytes();
    testByteReaderRoundTrip();
    testByteReaderLatchesOnTruncation();
    testPutFieldExactBytes();
    testFieldRoundTripIncludingSeparators();
    testLiteralMismatchFails();
    testFieldRejectsMalformedLengths();
    testFailureLatchesOk();
    testFieldInt64AcceptsAndRejects();
    testFieldIntRejectsOutOfIntRange();
    testFieldSizeT();
    testFieldDoubleRoundTrip();
    testParseUnsignedDecimal();

    if (g_fail == 0) std::printf("wire: all tests passed\n");
    else             std::printf("wire: %d CHECK(s) FAILED\n", g_fail);
    return g_fail == 0 ? 0 : 1;
}
