use super::*;
use crate::fasta::end_of_last_fasta_entry;
use crate::fastq::end_of_last_fastq_entry;

fn fasta_iter(data: &'static [u8], block_size: usize) -> SeqChunkIter<&'static [u8]> {
    SeqChunkIter::new(data, block_size, end_of_last_fasta_entry, None)
}

fn fastq_iter(data: &'static [u8], block_size: usize) -> SeqChunkIter<&'static [u8]> {
    SeqChunkIter::new(data, block_size, end_of_last_fastq_entry, None)
}

fn rope_to_vec(rope: &Rope) -> Vec<u8> {
    rope.fw_cursor().collect()
}

// ── FASTA ─────────────────────────────────────────────────────────────────

#[test]
fn fasta_single_record_one_chunk() {
    let data: &[u8] = b">s1\nACGT\n";
    let chunks: Vec<_> = fasta_iter(data, 64).collect::<Result<_, _>>().unwrap();
    assert_eq!(chunks.len(), 1);
    assert_eq!(rope_to_vec(&chunks[0]), b">s1\nACGT\n");
}

#[test]
fn fasta_two_records_split_across_chunks() {
    let data: &[u8] = b">s1\nACGT\n>s2\nTTTT\n";
    let chunks: Vec<_> = fasta_iter(data, 10).collect::<Result<_, _>>().unwrap();
    let all: Vec<u8> = chunks.iter().flat_map(|r| rope_to_vec(r)).collect();
    assert_eq!(all, b">s1\nACGT\n>s2\nTTTT\n");
}

#[test]
fn fasta_each_chunk_ends_on_complete_record() {
    let data: &[u8] = b">s1\nACGT\n>s2\nCCCC\n>s3\nGGGG\n>s4\nTTTT\n";
    for block in [8, 12, 20, 100] {
        let chunks: Vec<_> = fasta_iter(data, block).collect::<Result<_, _>>().unwrap();
        for rope in &chunks {
            let flat = rope_to_vec(rope);
            assert_eq!(flat[0], b'>', "block={block}: chunk doesn't start with '>'");
            assert_eq!(
                *flat.last().unwrap(),
                b'\n',
                "block={block}: chunk doesn't end with newline"
            );
        }
    }
}

// ── FASTQ ─────────────────────────────────────────────────────────────────

fn make_fastq(records: &[(&[u8], &[u8])]) -> Vec<u8> {
    let mut buf = Vec::new();
    for (seq, qual) in records {
        buf.extend_from_slice(b"@hdr\n");
        buf.extend_from_slice(seq);
        buf.push(b'\n');
        buf.extend_from_slice(b"+\n");
        buf.extend_from_slice(qual);
        buf.push(b'\n');
    }
    buf
}

#[test]
fn fastq_single_record_one_chunk() {
    let data = Box::leak(make_fastq(&[(b"ACGT", b"IIII")]).into_boxed_slice());
    let chunks: Vec<_> = fastq_iter(data, 64).collect::<Result<_, _>>().unwrap();
    assert_eq!(chunks.len(), 1);
}

#[test]
fn fastq_at_in_quality_handled() {
    let data = Box::leak(
        make_fastq(&[(b"ACGTACGT", b"@@@@IIII"), (b"TTTTTTTT", b"HHHHHHHH")])
            .into_boxed_slice(),
    );
    let chunks: Vec<_> = fastq_iter(data, 16).collect::<Result<_, _>>().unwrap();
    let all: Vec<u8> = chunks.iter().flat_map(|r| rope_to_vec(r)).collect();
    assert_eq!(all, *data);
}

#[test]
fn fastq_each_chunk_starts_with_at() {
    let data = Box::leak(
        make_fastq(&[
            (b"ACGT", b"IIII"),
            (b"CCCC", b"JJJJ"),
            (b"GGGG", b"KKKK"),
            (b"TTTT", b"LLLL"),
        ])
        .into_boxed_slice(),
    );
    for block in [18, 30, 60] {
        let chunks: Vec<_> = fastq_iter(data, block).collect::<Result<_, _>>().unwrap();
        for rope in &chunks {
            let first_byte = rope_to_vec(rope)[0];
            assert_eq!(
                first_byte, b'@',
                "block={block}: chunk doesn't start with '@'"
            );
        }
    }
}
