use std::fmt::Write as _;
use std::hint::black_box;

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

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

fn build_exl(entry_count: usize) -> Vec<u8> {
    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()
}

const COL_INT32: u16 = 0x0006;
const COL_UINT32: u16 = 0x0007;
const COL_UINT16: u16 = 0x0005;
const COL_UINT8: u16 = 0x0003;
const COL_BOOL: u16 = 0x0001;
const COL_STRING: u16 = 0x0000;
const COL_PACKED0: u16 = 0x0019;
const COL_PACKED1: u16 = 0x001A;
const COL_PACKED2: u16 = 0x001B;
const COL_PACKED3: u16 = 0x001C;

fn build_item_like_exh(pages: &[(u32, u32)], languages: &[u8]) -> Vec<u8> {
    let columns: &[(u16, u16)] = &[
        (COL_INT32, 0),
        (COL_INT32, 4),
        (COL_INT32, 8),
        (COL_INT32, 12),
        (COL_INT32, 16),
        (COL_INT32, 20),
        (COL_INT32, 24),
        (COL_INT32, 28),
        (COL_INT32, 32),
        (COL_INT32, 36),
        (COL_UINT32, 40),
        (COL_UINT32, 44),
        (COL_UINT32, 48),
        (COL_UINT32, 52),
        (COL_UINT16, 56),
        (COL_UINT16, 58),
        (COL_UINT16, 60),
        (COL_UINT16, 62),
        (COL_UINT8, 64),
        (COL_UINT8, 65),
        (COL_BOOL, 66),
        (COL_STRING, 68),
        (COL_PACKED0, 72),
        (COL_PACKED1, 72),
        (COL_PACKED2, 72),
        (COL_PACKED3, 72),
        (0x001D, 72),
        (0x001E, 72),
        (0x001F, 72),
        (0x0020, 72),
    ];
    let row_size: u16 = 76;
    let row_count: u32 = pages.iter().map(|(_, rc)| rc).sum();

    let col_count = u16::try_from(columns.len()).expect("column count fits u16");
    let page_count = u16::try_from(pages.len()).expect("page count fits u16");
    let lang_count = u16::try_from(languages.len()).expect("language 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.to_be_bytes());
    buf.extend_from_slice(&col_count.to_be_bytes());
    buf.extend_from_slice(&page_count.to_be_bytes());
    buf.extend_from_slice(&lang_count.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());
    }
    for (start_id, rc) in pages {
        buf.extend_from_slice(&start_id.to_be_bytes());
        buf.extend_from_slice(&rc.to_be_bytes());
    }
    for &lang in languages {
        buf.push(lang);
        buf.push(0);
    }
    buf
}

fn build_item_like_exd(row_count: usize) -> Vec<u8> {
    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;

    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("index_size fits u32")
            .to_be_bytes(),
    );

    for i in 0..row_count {
        let row_id = u32::try_from(i).expect("row_id 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("row_offset fits u32")
                .to_be_bytes(),
        );

        let data_size = u32::try_from(PAYLOAD_SIZE).expect("PAYLOAD_SIZE 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;
        buf[struct_start + 68..struct_start + 72].copy_from_slice(&0u32.to_be_bytes());

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

    buf
}

fn bench_exl_parse(c: &mut Criterion) {
    let small = build_exl(50);
    let large = build_exl(800);

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

    group.bench_function("small_50", |b| {
        b.iter(|| {
            let list = ExcelList::parse(black_box(&small)).expect("parse");
            black_box(list);
        });
    });

    group.bench_function("large_800", |b| {
        b.iter(|| {
            let list = ExcelList::parse(black_box(&large)).expect("parse");
            black_box(list);
        });
    });

    group.finish();
}

fn bench_exh_parse(c: &mut Criterion) {
    let pages: &[(u32, u32)] = &[
        (0, 8_000),
        (8_000, 8_000),
        (16_000, 8_000),
        (24_000, 8_000),
        (32_000, 8_000),
    ];
    let languages: &[u8] = &[1, 2, 3, 4];

    let exh_bytes = build_item_like_exh(pages, languages);

    let mut group = c.benchmark_group("excel.exh.parse");

    group.bench_function("item_like_30col_5page_4lang", |b| {
        b.iter(|| {
            let header = ExcelHeader::parse(black_box(&exh_bytes)).expect("parse");
            black_box(header);
        });
    });

    group.finish();
}

fn bench_exd_parse(c: &mut Criterion) {
    let exh_bytes = build_item_like_exh(&[(0, 8_000)], &[0]);
    let header = ExcelHeader::parse(&exh_bytes).expect("build EXH for EXD bench");

    let small_exd = build_item_like_exd(500);
    let large_exd = build_item_like_exd(8_000);

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

    group.bench_function("rows_500", |b| {
        b.iter(|| {
            let data = ExcelData::parse(black_box(small_exd.as_slice()), &header, Language::None)
                .expect("parse");
            black_box(data);
        });
    });

    group.bench_function("rows_8000", |b| {
        b.iter(|| {
            let data = ExcelData::parse(black_box(large_exd.as_slice()), &header, Language::None)
                .expect("parse");
            black_box(data);
        });
    });

    group.finish();
}

criterion_group!(benches, bench_exl_parse, bench_exh_parse, bench_exd_parse);
criterion_main!(benches);
