use std::{
    hint::black_box,
    io::{Cursor, Read, Write},
};

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

// ── Shared test data generator ────────────────────────────────────

fn make_data(size: usize) -> Vec<u8> {
    let mut v = Vec::with_capacity(size);
    for i in 0..size {
        v.push((i.wrapping_mul(0x9E37_79B9).wrapping_add(0x517C_C1B7)) as u8);
    }
    v
}

// ── shua_zip helpers ──────────────────────────────────────────────

fn shua_build(count: usize, size: usize) -> Vec<u8> {
    let mut archive = shua_zip::ZipArchive::new();
    let data = make_data(size);
    for i in 0..count {
        archive.add_file(format!("file_{:06}.bin", i), data.clone());
    }
    archive.build().unwrap()
}

fn shua_parse_and_read_all(zip_data: &[u8]) {
    let reader = SliceReader { data: zip_data };
    let archive = shua_zip::ZipArchive::new().with_binary(&reader).unwrap();
    for i in 0..archive.entries().len() {
        let _ = archive
            .read_file_with_strategy(i, &reader, shua_zip::ZipReadStrategy::Merged)
            .unwrap();
    }
}

// ── zip-rs helpers ────────────────────────────────────────────────

fn zip_build(count: usize, size: usize) -> Vec<u8> {
    let data = make_data(size);
    let buf = Cursor::new(Vec::new());
    let mut writer = zip::ZipWriter::new(buf);
    let options: zip::write::FileOptions<'_, ()> =
        zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored);
    for i in 0..count {
        writer
            .start_file(format!("file_{:06}.bin", i), options)
            .unwrap();
        writer.write_all(&data).unwrap();
    }
    writer.finish().unwrap().into_inner()
}

fn zip_parse_and_read_all(zip_data: &[u8]) {
    let mut archive = zip::ZipArchive::new(Cursor::new(zip_data)).unwrap();
    for i in 0..archive.len() {
        let mut file = archive.by_index(i).unwrap();
        let mut buf = Vec::new();
        file.read_to_end(&mut buf).unwrap();
    }
}

// ── ReadAt impl for in-memory data ────────────────────────────────

struct SliceReader<'a> {
    data: &'a [u8],
}

impl shua_zip::ReadAt for SliceReader<'_> {
    type Error = std::io::Error;

    fn size(&self) -> u64 {
        self.data.len() as u64
    }

    fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<(), Self::Error> {
        let start = offset as usize;
        let end = start + buf.len();
        buf.copy_from_slice(&self.data[start..end]);
        Ok(())
    }
}

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

fn bench_build_stored(c: &mut Criterion) {
    let mut group = c.benchmark_group("compare/build");
    group.sample_size(30);

    for &(count, size, label) in &[
        (1, 1024, "1x1KB"),
        (100, 1024, "100x1KB"),
        (1000, 1024, "1000x1KB"),
        (10, 1048576, "10x1MB"),
        (1, 104857600, "1x100MB"),
    ] {
        group.bench_function(format!("shua_zip/{}", label), |b| {
            b.iter(|| {
                black_box(shua_build(count, size));
            });
        });
        group.bench_function(format!("zip-rs/{}", label), |b| {
            b.iter(|| {
                black_box(zip_build(count, size));
            });
        });
    }
    group.finish();
}

fn bench_parse(c: &mut Criterion) {
    let mut group = c.benchmark_group("compare/parse");
    group.sample_size(30);

    for &(count, size, label) in &[
        (10, 1024, "10_entries()"),
        (100, 1024, "100_entries()"),
        (1000, 1024, "1000_entries()"),
        (10, 1048576, "10x1MB"),
    ] {
        // Build with shua_zip, parse with both (same byte content, different tools)
        let zip_data = shua_build(count, size);
        group.bench_function(format!("shua_zip/{}", label), |b| {
            let reader = SliceReader { data: &zip_data };
            b.iter(|| {
                shua_zip::ZipArchive::new()
                    .with_binary(black_box(&reader))
                    .unwrap();
            });
        });
        group.bench_function(format!("zip-rs/{}", label), |b| {
            b.iter(|| {
                zip::ZipArchive::new(black_box(Cursor::new(&zip_data))).unwrap();
            });
        });
    }
    group.finish();
}

fn bench_read_data(c: &mut Criterion) {
    let mut group = c.benchmark_group("compare/read_data");
    group.sample_size(30);

    for &(count, size, label) in &[(500, 4096, "500x4KB"), (10, 1048576, "10x1MB")] {
        let zip_data = shua_build(count, size);
        let cursor_for_parse = Cursor::new(&zip_data);
        let mut zip_archive = zip::ZipArchive::new(cursor_for_parse).unwrap();

        let reader = SliceReader { data: &zip_data };
        let shua_archive = shua_zip::ZipArchive::new().with_binary(&reader).unwrap();

        group.bench_function(format!("shua_zip/{}", label), |b| {
            b.iter(|| {
                for i in 0..shua_archive.entries().len() {
                    black_box(
                        shua_archive
                            .read_file_with_strategy(i, &reader, shua_zip::ZipReadStrategy::Merged)
                            .unwrap(),
                    );
                }
            });
        });
        group.bench_function(format!("zip-rs/{}", label), |b| {
            b.iter(|| {
                for i in 0..zip_archive.len() {
                    let mut file = zip_archive.by_index(i).unwrap();
                    let mut buf = Vec::new();
                    file.read_to_end(&mut buf).unwrap();
                    black_box(buf);
                }
            });
        });
    }
    group.finish();
}

fn bench_roundtrip(c: &mut Criterion) {
    let mut group = c.benchmark_group("compare/roundtrip");
    group.sample_size(20);

    for &(count, size, label) in &[
        (100, 1024, "100x1KB"),
        (1000, 256, "1000x256B"),
        (10, 1048576, "10x1MB"),
    ] {
        let zip_data = shua_build(count, size);

        group.bench_function(format!("shua_zip/{}", label), |b| {
            b.iter(|| {
                shua_parse_and_read_all(black_box(&zip_data));
            });
        });
        group.bench_function(format!("zip-rs/{}", label), |b| {
            b.iter(|| {
                zip_parse_and_read_all(black_box(&zip_data));
            });
        });
    }
    group.finish();
}

criterion_group! {
    name = benches;
    config = Criterion::default().significance_level(0.01).noise_threshold(0.02);
    targets =
        bench_build_stored,
        bench_parse,
        bench_read_data,
        bench_roundtrip,
}
criterion_main!(benches);
