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};

const ROW_SIZE: usize = 76;
const TRAILING_LEN: usize = 50;
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_decode_exh(row_count: u32) -> Vec<u8> {
    let columns: &[(u16, u16)] = &[
        (0x0006, 0),
        (0x0006, 4),
        (0x0006, 8),
        (0x0006, 12),
        (0x0006, 16),
        (0x0006, 20),
        (0x0006, 24),
        (0x0006, 28),
        (0x0006, 32),
        (0x0006, 36),
        (0x0007, 40),
        (0x0007, 44),
        (0x0007, 48),
        (0x0007, 52),
        (0x0005, 56),
        (0x0005, 58),
        (0x0005, 60),
        (0x0005, 62),
        (0x0003, 64),
        (0x0003, 65),
        (0x0001, 66),
        (0x0000, 68),
        (0x0019, 72),
        (0x001A, 72),
        (0x001B, 72),
        (0x001C, 72),
        (0x001D, 72),
        (0x001E, 72),
        (0x001F, 72),
        (0x0020, 72),
    ];
    let row_size_wire = u16::try_from(ROW_SIZE).expect("ROW_SIZE fits u16");
    let col_count = u16::try_from(columns.len()).expect("col count 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(&col_count.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());
    for (ty, off) in columns {
        buf.extend_from_slice(&ty.to_be_bytes());
        buf.extend_from_slice(&off.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_decode_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());

        let struct_start = row_offset + 6;
        for col in 0..10usize {
            let val = i32::try_from(i + col).unwrap_or(i32::MAX);
            buf[struct_start + col * 4..struct_start + col * 4 + 4]
                .copy_from_slice(&val.to_be_bytes());
        }
        for (k, off) in [56usize, 58, 60, 62].iter().enumerate() {
            let val = u16::try_from((i + k) % 0x10000).unwrap_or(0);
            buf[struct_start + off..struct_start + off + 2].copy_from_slice(&val.to_be_bytes());
        }
        buf[struct_start + 66] = u8::from(i % 2 == 0);
        buf[struct_start + 68..struct_start + 72].copy_from_slice(&0u32.to_be_bytes());
        buf[struct_start + 72] = u8::try_from(i % 256).unwrap_or(0);

        let trailing_start = struct_start + ROW_SIZE;
        let label = format!("I{i:010}");
        let lb = label.as_bytes();
        let copy_len = lb.len().min(12);
        buf[trailing_start..trailing_start + copy_len].copy_from_slice(&lb[..copy_len]);
    }

    buf
}

fn bench_row_field_single(c: &mut Criterion) {
    const ROW_COUNT: usize = 100;
    let header_wire = build_decode_exh(u32::try_from(ROW_COUNT).expect("fits u32"));
    let header = ExcelHeader::parse(&header_wire).expect("build EXH");
    let page_bytes = build_decode_exd(ROW_COUNT);
    let data = ExcelData::parse(&page_bytes, &header, Language::None).expect("build EXD");

    let mut group = c.benchmark_group("excel.decode.row_field");

    group.bench_with_input(
        BenchmarkId::from_parameter("scalar_col_int32"),
        &data,
        |b, d| {
            b.iter(|| {
                let row = d.row_at(black_box(0)).expect("row 0");
                let field = row.field(black_box(0)).expect("field 0");
                black_box(field);
            });
        },
    );

    group.bench_with_input(BenchmarkId::from_parameter("string_col"), &data, |b, d| {
        b.iter(|| {
            let row = d.row_at(black_box(0)).expect("row 0");
            let field = row.field(black_box(21)).expect("field 21");
            black_box(field);
        });
    });

    group.finish();
}

fn bench_all_fields_sweep(c: &mut Criterion) {
    const ROW_COUNT: usize = 100;
    let header_wire = build_decode_exh(u32::try_from(ROW_COUNT).expect("fits u32"));
    let header = ExcelHeader::parse(&header_wire).expect("build EXH");
    let page_bytes = build_decode_exd(ROW_COUNT);
    let data = ExcelData::parse(&page_bytes, &header, Language::None).expect("build EXD");

    let mut group = c.benchmark_group("excel.decode.all_fields");

    group.bench_function("row_30cols", |b| {
        b.iter(|| {
            let row = data.row_at(black_box(0)).expect("row 0");
            for col in 0..row.column_count() {
                let field = row.field(black_box(col)).expect("field");
                black_box(field);
            }
        });
    });

    group.finish();
}

criterion_group!(benches, bench_row_field_single, bench_all_fields_sweep);
criterion_main!(benches);
