use std::hint::black_box;

use codspeed_criterion_compat::{BenchmarkId, Criterion, criterion_group, criterion_main};

use sqpack_rs::Language;
use sqpack_rs::excel::{ExcelData, ExcelHeader, ExcelList};

const ROW_SIZE: usize = 76;
const TRAILING_LEN: usize = 20;
const PAYLOAD_SIZE: usize = ROW_SIZE + TRAILING_LEN;
const ROW_WIRE_SIZE: usize = 6 + PAYLOAD_SIZE;
const EXD_HEADER_SIZE: usize = 0x20;
const INDEX_ENTRY_SIZE: usize = 8;

fn build_iter_exh(row_count: u32) -> Vec<u8> {
    let row_size_wire = u16::try_from(ROW_SIZE).expect("ROW_SIZE fits u16");
    let mut buf = Vec::new();
    buf.extend_from_slice(b"EXHF");
    buf.extend_from_slice(&3u16.to_be_bytes());
    buf.extend_from_slice(&row_size_wire.to_be_bytes());
    buf.extend_from_slice(&1u16.to_be_bytes());
    buf.extend_from_slice(&1u16.to_be_bytes());
    buf.extend_from_slice(&1u16.to_be_bytes());
    buf.extend_from_slice(&0u16.to_be_bytes());
    buf.push(0u8);
    buf.push(1u8);
    buf.extend_from_slice(&0u16.to_be_bytes());
    buf.extend_from_slice(&row_count.to_be_bytes());
    buf.extend_from_slice(&0u32.to_be_bytes());
    buf.extend_from_slice(&0u32.to_be_bytes());
    buf.extend_from_slice(&0x0006u16.to_be_bytes());
    buf.extend_from_slice(&0u16.to_be_bytes());
    buf.extend_from_slice(&0u32.to_be_bytes());
    buf.extend_from_slice(&row_count.to_be_bytes());
    buf.push(0u8);
    buf.push(0u8);
    buf
}

fn build_iter_exd(row_count: usize) -> Vec<u8> {
    let index_size = row_count * INDEX_ENTRY_SIZE;
    let data_region_start = EXD_HEADER_SIZE + index_size;
    let total_size = data_region_start + row_count * ROW_WIRE_SIZE;

    let mut buf = vec![0u8; total_size];

    buf[0..4].copy_from_slice(b"EXDF");
    buf[4..6].copy_from_slice(&2u16.to_be_bytes());
    buf[8..12].copy_from_slice(&u32::try_from(index_size).expect("fits u32").to_be_bytes());

    for i in 0..row_count {
        let row_id = u32::try_from(i).expect("fits u32");
        let row_offset = data_region_start + i * ROW_WIRE_SIZE;

        let idx_off = EXD_HEADER_SIZE + i * INDEX_ENTRY_SIZE;
        buf[idx_off..idx_off + 4].copy_from_slice(&row_id.to_be_bytes());
        buf[idx_off + 4..idx_off + 8]
            .copy_from_slice(&u32::try_from(row_offset).expect("fits u32").to_be_bytes());

        let data_size = u32::try_from(PAYLOAD_SIZE).expect("fits u32");
        buf[row_offset..row_offset + 4].copy_from_slice(&data_size.to_be_bytes());
        buf[row_offset + 4..row_offset + 6].copy_from_slice(&1u16.to_be_bytes());
    }

    buf
}

fn build_iter_exl(entry_count: usize) -> Vec<u8> {
    use std::fmt::Write as _;
    let mut s = String::from("EXLT,2\n");
    for i in 0..entry_count {
        writeln!(s, "Sheet{i},{i}").expect("String write is infallible");
    }
    s.into_bytes()
}

fn bench_exd_rows_iter(c: &mut Criterion) {
    const ROW_COUNT: usize = 8_000;
    let header_wire = build_iter_exh(u32::try_from(ROW_COUNT).expect("fits u32"));
    let header = ExcelHeader::parse(&header_wire).expect("build EXH");
    let page_bytes = build_iter_exd(ROW_COUNT);
    let data = ExcelData::parse(&page_bytes, &header, Language::None).expect("build EXD");

    let mut group = c.benchmark_group("excel.exd.iter");

    group.bench_function("rows_8000_full_sweep", |b| {
        b.iter(|| {
            let mut count: usize = 0;
            for row in data.rows() {
                count += black_box(row.row_id()) as usize;
            }
            black_box(count);
        });
    });

    group.finish();
}

fn bench_exd_row_lookup(c: &mut Criterion) {
    const ROW_COUNT: usize = 8_000;
    let header_wire = build_iter_exh(u32::try_from(ROW_COUNT).expect("fits u32"));
    let header = ExcelHeader::parse(&header_wire).expect("build EXH");
    let page_bytes = build_iter_exd(ROW_COUNT);
    let data = ExcelData::parse(&page_bytes, &header, Language::None).expect("build EXD");

    let mut group = c.benchmark_group("excel.exd.row_lookup");

    for (label, row_id) in [("first", 0u32), ("middle", 4_000u32), ("last", 7_999u32)] {
        group.bench_with_input(BenchmarkId::from_parameter(label), &row_id, |b, &id| {
            b.iter(|| {
                let row = data.row(black_box(id));
                black_box(row);
            });
        });
    }

    group.finish();
}

fn bench_exl_iter_and_get(c: &mut Criterion) {
    const ENTRY_COUNT: usize = 800;
    let exl_bytes = build_iter_exl(ENTRY_COUNT);
    let list = ExcelList::parse(&exl_bytes).expect("build EXL");

    let mut group = c.benchmark_group("excel.exl.iter");

    group.bench_function("full_sweep_800", |b| {
        b.iter(|| {
            let mut count: usize = 0;
            for (name, id) in list.iter() {
                count += black_box(name).len();
                count += black_box(id).map_or(0, |v| v as usize);
            }
            black_box(count);
        });
    });

    group.bench_function("get_hit_800", |b| {
        let target = "Sheet400";
        b.iter(|| {
            let id = list.get(black_box(target));
            black_box(id);
        });
    });

    group.bench_function("get_miss_800", |b| {
        let absent = "Nonexistent";
        b.iter(|| {
            let id = list.get(black_box(absent));
            black_box(id);
        });
    });

    group.finish();
}

criterion_group!(
    benches,
    bench_exd_rows_iter,
    bench_exd_row_lookup,
    bench_exl_iter_and_get
);
criterion_main!(benches);
