use super::*;

fn make_rope(data: &[u8]) -> Rope {
    let mut r = Rope::new(None);
    r.push(data.to_vec());
    r
}

fn flat(r: Rope) -> Vec<u8> {
    r.fw_cursor().collect()
}

fn run_fastq(data: &[u8], k: usize) -> Vec<u8> {
    flat(normalize_fastq_chunk(make_rope(data), k))
}

fn run_fasta(data: &[u8], k: usize) -> Vec<u8> {
    flat(normalize_fasta_chunk(make_rope(data), k))
}

fn make_fastq(records: &[&[u8]]) -> Vec<u8> {
    let mut buf = Vec::new();
    for seq 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(&vec![b'I'; seq.len()]);
        buf.push(b'\n');
    }
    buf
}

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

// ── FASTQ basic ──────────────────────────────────────────────────────────

#[test]
fn single_record_produces_seq_then_null() {
    assert_eq!(run_fastq(&make_fastq(&[b"ACGTACGT"]), 4), b"ACGTACGT\x00");
}

#[test]
fn two_records_concatenated() {
    assert_eq!(
        run_fastq(&make_fastq(&[b"ACGTACGT", b"TTTTTTTT"]), 4),
        b"ACGTACGT\x00TTTTTTTT\x00"
    );
}

#[test]
fn lowercase_input_uppercased() {
    assert_eq!(run_fastq(&make_fastq(&[b"acgtacgt"]), 4), b"ACGTACGT\x00");
}

#[test]
fn mixed_case_uppercased() {
    assert_eq!(run_fastq(&make_fastq(&[b"AcGtAcGt"]), 4), b"ACGTACGT\x00");
}

#[test]
fn sequence_shorter_than_k_discarded() {
    assert_eq!(run_fastq(&make_fastq(&[b"ACG"]), 4), b"");
}

#[test]
fn sequence_exactly_k_kept() {
    assert_eq!(run_fastq(&make_fastq(&[b"ACGT"]), 4), b"ACGT\x00");
}

#[test]
fn short_record_among_valid_ones_discarded() {
    assert_eq!(
        run_fastq(&make_fastq(&[b"ACGTACGT", b"AC", b"TTTTTTTT"]), 4),
        b"ACGTACGT\x00TTTTTTTT\x00"
    );
}

#[test]
fn ambiguous_splits_into_two_segments() {
    assert_eq!(
        run_fastq(&make_fastq(&[b"ACGTNACGT"]), 4),
        b"ACGT\x00ACGT\x00"
    );
}

#[test]
fn segment_after_ambiguous_too_short_discarded() {
    assert_eq!(
        run_fastq(&make_fastq(&[b"ACGTACGTNAC"]), 4),
        b"ACGTACGT\x00"
    );
}

#[test]
fn consecutive_ambiguous_produce_no_empty_segment() {
    assert_eq!(
        run_fastq(&make_fastq(&[b"ACGTNNNNACGT"]), 4),
        b"ACGT\x00ACGT\x00"
    );
}

#[test]
fn ambiguous_at_start_skipped() {
    assert_eq!(run_fastq(&make_fastq(&[b"NNACGTACGT"]), 4), b"ACGTACGT\x00");
}

#[test]
fn ambiguous_at_end_produces_no_trailing_empty() {
    assert_eq!(run_fastq(&make_fastq(&[b"ACGTACGTNN"]), 4), b"ACGTACGT\x00");
}

#[test]
fn crlf_handled() {
    let data = b"@hdr\r\nACGTACGT\r\n+\r\nIIIIIIII\r\n";
    assert_eq!(run_fastq(data, 4), b"ACGTACGT\x00");
}

#[test]
fn multi_slice_rope() {
    let data = make_fastq(&[b"ACGTACGT", b"TTTTTTTT"]);
    let mid = data.len() / 2;
    let mut rope = Rope::new(None);
    rope.push(data[..mid].to_vec());
    rope.push(data[mid..].to_vec());
    assert_eq!(
        flat(normalize_fastq_chunk(rope, 4)),
        b"ACGTACGT\x00TTTTTTTT\x00"
    );
}

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

#[test]
fn fasta_single_record() {
    assert_eq!(
        run_fasta(&make_fasta(&[(b"s1", b"ACGTACGT")]), 4),
        b"ACGTACGT\x00"
    );
}

#[test]
fn fasta_two_records() {
    assert_eq!(
        run_fasta(
            &make_fasta(&[(b"s1", b"ACGTACGT"), (b"s2", b"TTTTTTTT")]),
            4
        ),
        b"ACGTACGT\x00TTTTTTTT\x00"
    );
}

#[test]
fn fasta_multiline_sequence_concatenated() {
    assert_eq!(
        run_fasta(b">s1\nACGT\nACGT\nACGT\n", 4),
        b"ACGTACGTACGT\x00"
    );
}

#[test]
fn fasta_lowercase_uppercased() {
    assert_eq!(
        run_fasta(&make_fasta(&[(b"s1", b"acgtacgt")]), 4),
        b"ACGTACGT\x00"
    );
}

#[test]
fn fasta_short_record_discarded() {
    assert_eq!(run_fasta(&make_fasta(&[(b"s1", b"ACG")]), 4), b"");
}

#[test]
fn fasta_short_among_valid_discarded() {
    assert_eq!(
        run_fasta(
            &make_fasta(&[(b"s1", b"ACGTACGT"), (b"s2", b"AC"), (b"s3", b"TTTTTTTT")]),
            4
        ),
        b"ACGTACGT\x00TTTTTTTT\x00"
    );
}

#[test]
fn fasta_ambiguous_splits_segments() {
    assert_eq!(run_fasta(b">s1\nACGTNACGT\n", 4), b"ACGT\x00ACGT\x00");
}

#[test]
fn fasta_ambiguous_across_line_boundary() {
    assert_eq!(run_fasta(b">s1\nACGT\nNACGT\n", 4), b"ACGT\x00ACGT\x00");
}

#[test]
fn fasta_ambiguous_short_segment_discarded() {
    assert_eq!(run_fasta(b">s1\nACGTACGTNAC\n", 4), b"ACGTACGT\x00");
}

#[test]
fn fasta_no_trailing_newline() {
    assert_eq!(run_fasta(b">s1\nACGTACGT", 4), b"ACGTACGT\x00");
}

#[test]
fn fasta_crlf_line_endings() {
    assert_eq!(
        run_fasta(b">s1\r\nACGT\r\nACGT\r\n>s2\r\nTTTT\r\n", 4),
        b"ACGTACGT\x00TTTT\x00"
    );
}

#[test]
fn fasta_multi_slice_rope() {
    let data = make_fasta(&[(b"s1", b"ACGTACGT"), (b"s2", b"TTTTTTTT")]);
    let mid = data.len() / 2;
    let mut rope = Rope::new(None);
    rope.push(data[..mid].to_vec());
    rope.push(data[mid..].to_vec());
    assert_eq!(
        flat(normalize_fasta_chunk(rope, 4)),
        b"ACGTACGT\x00TTTTTTTT\x00"
    );
}
