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

use shua_zip::{ReadAt, ZipArchive, ZipReadStrategy};

// ── Slice reader (ReadAt for in-memory bytes) ────────────────────────

struct SliceReader<'a>(&'a [u8]);

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

    fn size(&self) -> u64 {
        self.0.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.0[start..end]);
        Ok(())
    }
}

// ── Helpers ──────────────────────────────────────────────────────────

fn make_archive_store(files: &[(&str, &[u8])]) -> Vec<u8> {
    let mut archive = ZipArchive::new();
    for &(name, data) in files {
        archive.add_file(name, data);
    }
    archive.build().unwrap()
}

fn shua_read_names(data: &[u8]) -> Vec<String> {
    let reader = SliceReader(data);
    let archive = ZipArchive::new().with_binary(&reader).unwrap();
    archive.entries().iter().map(|e| e.name.clone()).collect()
}

fn shua_read_all(data: &[u8]) -> Vec<(String, Vec<u8>)> {
    let reader = SliceReader(data);
    let archive = ZipArchive::new().with_binary(&reader).unwrap();
    let mut out = Vec::new();
    for i in 0..archive.entries().len() {
        let bytes = archive
            .read_file_with_strategy(i, &reader, ZipReadStrategy::Merged)
            .unwrap();
        out.push((archive.entries()[i].name.clone(), bytes));
    }
    out
}

fn zip_read_names(data: &[u8]) -> Vec<String> {
    let mut archive = zip::ZipArchive::new(Cursor::new(data)).unwrap();
    let mut names = Vec::new();
    for i in 0..archive.len() {
        names.push(archive.by_index(i).unwrap().name().to_string());
    }
    names
}

fn zip_read_all(data: &[u8]) -> Vec<(String, Vec<u8>)> {
    let mut archive = zip::ZipArchive::new(Cursor::new(data)).unwrap();
    let mut out = Vec::new();
    for i in 0..archive.len() {
        let mut file = archive.by_index(i).unwrap();
        let name = file.name().to_string();
        let mut buf = Vec::new();
        file.read_to_end(&mut buf).unwrap();
        out.push((name, buf));
    }
    out
}

fn zip_build_store(files: &[(&str, &[u8])]) -> Vec<u8> {
    let buf = Cursor::new(Vec::new());
    let mut writer = zip::ZipWriter::new(buf);
    let options =
        zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
    for &(name, data) in files {
        writer.start_file(name, options).unwrap();
        writer.write_all(data).unwrap();
    }
    writer.finish().unwrap().into_inner()
}

fn zip_build_deflate(files: &[(&str, &[u8])]) -> Vec<u8> {
    let buf = Cursor::new(Vec::new());
    let mut writer = zip::ZipWriter::new(buf);
    let options = zip::write::SimpleFileOptions::default()
        .compression_method(zip::CompressionMethod::Deflated);
    for &(name, data) in files {
        writer.start_file(name, options).unwrap();
        writer.write_all(data).unwrap();
    }
    writer.finish().unwrap().into_inner()
}

// ── Tests ────────────────────────────────────────────────────────────

#[test]
fn shua_roundtrip_store_single() {
    let data = make_data(4096);
    let zip_data = make_archive_store(&[("hello.txt", &data)]);
    let result = shua_read_all(&zip_data);
    assert_eq!(result.len(), 1);
    assert_eq!(result[0].0, "hello.txt");
    assert_eq!(result[0].1, data);
}

#[test]
fn shua_roundtrip_store_multiple() {
    let files: Vec<_> = (0..10)
        .map(|i| {
            let name = format!("file_{}.bin", i);
            let data = make_data(1024 + i * 13);
            (name, data)
        })
        .collect();
    let raw: Vec<(&str, &[u8])> = files
        .iter()
        .map(|(n, d)| (n.as_str(), d.as_slice()))
        .collect();
    let zip_data = make_archive_store(&raw);
    let result = shua_read_all(&zip_data);
    assert_eq!(result.len(), files.len());
    for (got, want) in result.iter().zip(files.iter()) {
        assert_eq!(got.0, want.0);
        assert_eq!(got.1, want.1);
    }
}

#[test]
fn zip_write_shua_read_deflate_single() {
    // shua_zip only creates store entries via its public API,
    // but must correctly read deflate entries created by zip-rs.
    let data = make_data(8192);
    let zip_data = zip_build_deflate(&[("world.bin", &data)]);
    let result = shua_read_all(&zip_data);
    assert_eq!(result.len(), 1);
    assert_eq!(result[0].1, data);
}

#[test]
fn shua_roundtrip_empty_file() {
    let zip_data = make_archive_store(&[("empty", &[])]);
    let result = shua_read_all(&zip_data);
    assert_eq!(result[0].1, b"");
}

#[test]
fn shua_write_zip_read_names() {
    let zip_data = make_archive_store(&[
        ("alpha.txt", b"aaa"),
        ("beta.txt", b"bbb"),
        ("gamma.txt", b"ccc"),
    ]);
    let names = zip_read_names(&zip_data);
    assert_eq!(names, vec!["alpha.txt", "beta.txt", "gamma.txt"]);
}

#[test]
fn shua_write_zip_read_content_store() {
    let data = make_data(555);
    let zip_data = make_archive_store(&[("data.bin", &data)]);
    let result = zip_read_all(&zip_data);
    assert_eq!(result[0].0, "data.bin");
    assert_eq!(result[0].1, data);
}

#[test]
fn zip_roundtrip_deflate_self_check() {
    // zip-rs deflate round-trip (sanity check for the test harness).
    let data = make_data(12345);
    let zip_data = zip_build_deflate(&[("compressed.bin", &data)]);
    let result = zip_read_all(&zip_data);
    assert_eq!(result[0].0, "compressed.bin");
    assert_eq!(result[0].1, data);
}

#[test]
fn zip_write_shua_read_names() {
    let data = b"hello world";
    let zip_data = zip_build_store(&[("a.txt", data), ("b.txt", data)]);
    let names = shua_read_names(&zip_data);
    assert_eq!(names, vec!["a.txt", "b.txt"]);
}

#[test]
fn zip_write_shua_read_content_store() {
    let data = make_data(9999);
    let zip_data = zip_build_store(&[("stored.bin", &data)]);
    let result = shua_read_all(&zip_data);
    assert_eq!(result[0].0, "stored.bin");
    assert_eq!(result[0].1, data);
}

#[test]
fn zip_write_shua_read_content_deflate() {
    let data = make_data(7777);
    let zip_data = zip_build_deflate(&[("deflated.bin", &data)]);
    let result = shua_read_all(&zip_data);
    assert_eq!(result[0].0, "deflated.bin");
    assert_eq!(result[0].1, data);
}

#[test]
fn zip_write_shua_read_multiple_mixed() {
    let d1 = make_data(512);
    let d2 = make_data(2048);
    let d3 = make_data(100);
    let zip_data = zip_build_store(&[("first.bin", &d1), ("second.bin", &d2), ("third.bin", &d3)]);
    let result = shua_read_all(&zip_data);
    assert_eq!(result.len(), 3);
    assert_eq!(result[0].1, d1);
    assert_eq!(result[1].1, d2);
    assert_eq!(result[2].1, d3);
}

#[test]
fn cross_store_large() {
    let data = make_data(1_000_000);
    let zip_data = make_archive_store(&[("large.bin", &data)]);
    // shua → shua
    let result = shua_read_all(&zip_data);
    assert_eq!(result[0].1, data);
    // shua → zip
    let result2 = zip_read_all(&zip_data);
    assert_eq!(result2[0].1, data);
}

#[test]
fn zip_deflate_shua_read_large() {
    let data = make_data(500_000);
    let zip_data = zip_build_deflate(&[("big_compressed.bin", &data)]);
    let result = shua_read_all(&zip_data);
    assert_eq!(result[0].1, data);
}

#[test]
fn zip_empty_file() {
    let zip_data = zip_build_store(&[("empty.txt", b"")]);
    let result = shua_read_all(&zip_data);
    assert_eq!(result[0].0, "empty.txt");
    assert_eq!(result[0].1, b"");
}

#[test]
fn unicode_filenames() {
    let data = b"unicode test";
    let zip_data = zip_build_store(&[
        ("你好世界.txt", data),
        ("привет.txt", data),
        ("emoji_🎉.txt", data),
    ]);
    let result = shua_read_all(&zip_data);
    assert_eq!(result[0].0, "你好世界.txt");
    assert_eq!(result[1].0, "привет.txt");
    assert_eq!(result[2].0, "emoji_🎉.txt");
}

#[test]
fn many_small_files() {
    let count = 50;
    let mut files = Vec::new();
    for i in 0..count {
        let name = format!("{:04}.dat", i);
        let data = make_data(128 + i % 64);
        files.push((name, data));
    }
    let raw: Vec<(&str, &[u8])> = files
        .iter()
        .map(|(n, d)| (n.as_str(), d.as_slice()))
        .collect();

    // shua → zip
    let zip_data = make_archive_store(&raw);
    let result = zip_read_all(&zip_data);
    assert_eq!(result.len(), count as usize);

    // zip → shua
    let zip_data2 = zip_build_store(&raw);
    let result2 = shua_read_all(&zip_data2);
    assert_eq!(result2.len(), count as usize);
    for (got, want) in result2.iter().zip(files.iter()) {
        assert_eq!(got.0, want.0);
        assert_eq!(got.1, want.1);
    }
}

#[test]
fn mixed_store_and_deflate_zip_write() {
    let stored = make_data(2048);
    let deflated = make_data(4096);
    let buf = Cursor::new(Vec::new());
    let mut writer = zip::ZipWriter::new(buf);
    let store_opts =
        zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
    let deflate_opts = zip::write::SimpleFileOptions::default()
        .compression_method(zip::CompressionMethod::Deflated);

    writer.start_file("stored.bin", store_opts).unwrap();
    writer.write_all(&stored).unwrap();
    writer.start_file("deflated.bin", deflate_opts).unwrap();
    writer.write_all(&deflated).unwrap();
    let zip_data = writer.finish().unwrap().into_inner();

    let result = shua_read_all(&zip_data);
    assert_eq!(result.len(), 2);
    assert_eq!(result[0].1, stored);
    assert_eq!(result[1].1, deflated);
}

// ── Repeatable deterministic "random" data ────────────────────────────

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
}
