#![allow(clippy::unwrap_used)]
//! Benchmark: Cap'n Proto vs Protobuf serialization for chat message envelopes.
//!
//! Compares serialization/deserialization speed and encoded size at three
//! payload sizes (100 B, 1 KB, 4 KB) for a typical Envelope{seq, data} message.

use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};

// ── Cap'n Proto path ────────────────────────────────────────────────────────

fn capnp_serialize_envelope(seq: u64, data: &[u8]) -> Vec<u8> {
    let mut msg = capnp::message::Builder::new_default();
    {
        let mut envelope = msg.init_root::<quicproquo_proto::node_capnp::envelope::Builder>();
        envelope.set_seq(seq);
        envelope.set_data(data);
    }
    quicproquo_proto::to_bytes(&msg).unwrap()
}

fn capnp_deserialize_envelope(bytes: &[u8]) -> (u64, Vec<u8>) {
    let reader = quicproquo_proto::from_bytes(bytes).unwrap();
    let envelope = reader
        .get_root::<quicproquo_proto::node_capnp::envelope::Reader>()
        .unwrap();
    (envelope.get_seq(), envelope.get_data().unwrap().to_vec())
}

// ── Protobuf path (hand-coded prost encoding to avoid build-dep) ────────────
//
// Envelope { seq: uint64 (field 1), data: bytes (field 2) }
// Wire format: varint tag + varint seq + len-delimited data

fn protobuf_serialize_envelope(seq: u64, data: &[u8]) -> Vec<u8> {
    // Build a prost message via raw encoding.
    // Field 1: uint64 seq, wire type 0 (varint), tag = (1 << 3) | 0 = 0x08
    // Field 2: bytes  data, wire type 2 (length-delimited), tag = (2 << 3) | 2 = 0x12
    let mut buf = Vec::with_capacity(10 + data.len());
    // Encode field 1 (seq)
    prost::encoding::uint64::encode(1, &seq, &mut buf);
    // Encode field 2 (data)
    prost::encoding::bytes::encode(2, &data.to_vec(), &mut buf);
    buf
}

fn protobuf_deserialize_envelope(bytes: &[u8]) -> (u64, Vec<u8>) {
    // Decode manually using prost wire format
    let mut seq: u64 = 0;
    let mut data: Vec<u8> = Vec::new();
    let mut buf = bytes;

    while !buf.is_empty() {
        let (tag, wire_type) =
            prost::encoding::decode_key(&mut buf).expect("decode key");
        match tag {
            1 => {
                prost::encoding::uint64::merge(wire_type, &mut seq, &mut buf, Default::default())
                    .expect("decode seq");
            }
            2 => {
                prost::encoding::bytes::merge(wire_type, &mut data, &mut buf, Default::default())
                    .expect("decode data");
            }
            _ => {
                prost::encoding::skip_field(wire_type, tag, &mut buf, Default::default())
                    .expect("skip unknown field");
            }
        }
    }
    (seq, data)
}

// ── Benchmarks ──────────────────────────────────────────────────────────────

fn bench_serialize(c: &mut Criterion) {
    let sizes: &[(&str, usize)] = &[("100B", 100), ("1KB", 1024), ("4KB", 4096)];
    let mut group = c.benchmark_group("serialize_envelope");

    for (label, size) in sizes {
        let payload = vec![0xABu8; *size];
        let seq = 42u64;

        group.bench_with_input(
            BenchmarkId::new("capnp", label),
            &(&seq, &payload),
            |b, &(seq, payload)| {
                b.iter(|| capnp_serialize_envelope(black_box(*seq), black_box(payload)));
            },
        );

        group.bench_with_input(
            BenchmarkId::new("protobuf", label),
            &(&seq, &payload),
            |b, &(seq, payload)| {
                b.iter(|| protobuf_serialize_envelope(black_box(*seq), black_box(payload)));
            },
        );
    }
    group.finish();
}

fn bench_deserialize(c: &mut Criterion) {
    let sizes: &[(&str, usize)] = &[("100B", 100), ("1KB", 1024), ("4KB", 4096)];
    let mut group = c.benchmark_group("deserialize_envelope");

    for (label, size) in sizes {
        let payload = vec![0xABu8; *size];
        let seq = 42u64;

        let capnp_bytes = capnp_serialize_envelope(seq, &payload);
        let proto_bytes = protobuf_serialize_envelope(seq, &payload);

        group.bench_with_input(
            BenchmarkId::new("capnp", label),
            &capnp_bytes,
            |b, bytes| {
                b.iter(|| capnp_deserialize_envelope(black_box(bytes)));
            },
        );

        group.bench_with_input(
            BenchmarkId::new("protobuf", label),
            &proto_bytes,
            |b, bytes| {
                b.iter(|| protobuf_deserialize_envelope(black_box(bytes)));
            },
        );
    }
    group.finish();
}

fn bench_encoded_sizes(c: &mut Criterion) {
    let sizes: &[(&str, usize)] = &[("100B", 100), ("1KB", 1024), ("4KB", 4096)];
    let mut group = c.benchmark_group("encoded_size");

    for (label, size) in sizes {
        let payload = vec![0xABu8; *size];
        let capnp_bytes = capnp_serialize_envelope(42, &payload);
        let proto_bytes = protobuf_serialize_envelope(42, &payload);

        // Use a trivial benchmark that just returns the size -- the point
        // is to get criterion to print the iteration count and allow
        // comparison. The real value is in the eprintln below.
        group.bench_with_input(
            BenchmarkId::new("capnp", label),
            &capnp_bytes,
            |b, bytes| {
                b.iter(|| black_box(bytes.len()));
            },
        );

        group.bench_with_input(
            BenchmarkId::new("protobuf", label),
            &proto_bytes,
            |b, bytes| {
                b.iter(|| black_box(bytes.len()));
            },
        );

        eprintln!(
            "  {label}: capnp={} bytes, protobuf={} bytes, overhead={:+} bytes",
            capnp_bytes.len(),
            proto_bytes.len(),
            capnp_bytes.len() as isize - proto_bytes.len() as isize,
        );
    }
    group.finish();
}

criterion_group!(benches, bench_serialize, bench_deserialize, bench_encoded_sizes);
criterion_main!(benches);
