// Benchmark harness: unwrap on known-good setup is idiomatic here.
#![allow(clippy::unwrap_used)]
//! Benchmark comparing `InProcessTransport` vs `GrpcTransport` throughput.
//!
//! Measures segments/sec for both transports to quantify gRPC overhead.

use std::{collections::HashMap, net::SocketAddr, thread, time::Duration};

use criterion::{Criterion, Throughput, criterion_group, criterion_main};
use tidal_net::{GrpcTransport, config::GrpcTransportConfig};
use tidaldb::replication::{
    WalSegmentId,
    in_process::InProcessTransportFactory,
    shard::{RegionId, ShardId},
    transport::{Transport, WalSegmentPayload},
};

const SEGMENT_SIZE: usize = 1024; // 1 KB payload per segment

fn make_payload(seqno: u64) -> WalSegmentPayload {
    WalSegmentPayload {
        id: WalSegmentId::new(RegionId::SINGLE, ShardId(0), seqno),
        bytes: vec![0xAB; SEGMENT_SIZE],
        event_count: 10,
        leader_last_seq: seqno,
    }
}

fn free_addr() -> SocketAddr {
    let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
    listener.local_addr().unwrap()
}

fn bench_transports(c: &mut Criterion) {
    let mut group = c.benchmark_group("transport_throughput");
    group.throughput(Throughput::Elements(1));

    // ── InProcessTransport ──────────────────────────────────────────────

    {
        let shards = vec![ShardId(0), ShardId(1)];
        let mut transports = InProcessTransportFactory::new(&shards).build();
        let t0 = transports.remove(&ShardId(0)).unwrap();
        let t1 = transports.remove(&ShardId(1)).unwrap();

        // Drain receiver in background.
        let drain = thread::spawn(move || while t1.recv_segment().is_some() {});

        let mut seq = 0u64;
        group.bench_function("in_process", |b| {
            b.iter(|| {
                seq += 1;
                t0.send_segment(ShardId(1), make_payload(seq)).unwrap();
            });
        });

        drop(t0);
        let _ = drain.join();
    }

    // ── GrpcTransport ───────────────────────────────────────────────────

    {
        let addr0 = free_addr();
        let addr1 = free_addr();

        let config0 = GrpcTransportConfig {
            local_shard: ShardId(0),
            listen_addr: addr0,
            peers: HashMap::from([(ShardId(1), addr1)]),
            insecure: true,
            ..Default::default()
        };
        let config1 = GrpcTransportConfig {
            local_shard: ShardId(1),
            listen_addr: addr1,
            peers: HashMap::from([(ShardId(0), addr0)]),
            insecure: true,
            ..Default::default()
        };

        let t0 = GrpcTransport::new(config0).expect("grpc transport 0");
        let t1 = GrpcTransport::new(config1).expect("grpc transport 1");
        thread::sleep(Duration::from_millis(200));

        // Drain receiver in background.
        let drain = thread::spawn(move || while t1.recv_segment().is_some() {});

        let mut seq = 0u64;
        group.bench_function("grpc_localhost", |b| {
            b.iter(|| {
                seq += 1;
                t0.send_segment(ShardId(1), make_payload(seq)).unwrap();
            });
        });

        drop(t0);
        let _ = drain.join();
    }

    group.finish();
}

criterion_group!(benches, bench_transports);
criterion_main!(benches);
