use super::*;
use std::io::Cursor;
use std::ops::Deref;

// ── helpers ───────────────────────────────────────────────────────────────

fn run_fasta(data: &[u8], k: usize) -> Vec<u8> {
    NucStream::<_, FastaParser>::new(Cursor::new(data.to_vec()), k)
        .flat_map(|p| p.deref().to_vec())
        .collect()
}

fn run_fastq(data: &[u8], k: usize) -> Vec<u8> {
    NucStream::<_, FastqParser>::new(Cursor::new(data.to_vec()), k)
        .flat_map(|p| p.deref().to_vec())
        .collect()
}

fn run_genbank(data: &[u8], k: usize) -> Vec<u8> {
    NucStream::<_, GenbankParser>::new(Cursor::new(data.to_vec()), k)
        .flat_map(|p| p.deref().to_vec())
        .collect()
}

fn pages_fasta(data: &[u8], k: usize) -> Vec<Vec<u8>> {
    NucStream::<_, FastaParser>::new(Cursor::new(data.to_vec()), k)
        .map(|p| p.deref().to_vec())
        .collect()
}

// ── FastaParser ───────────────────────────────────────────────────────────

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

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

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

#[test]
fn fasta_two_sequences() {
    let data = b">s1\nACGTACGT\n>s2\nTTTTTTTT\n";
    assert_eq!(run_fasta(data, 4), b"ACGTACGT\x00TTTTTTTT\x00");
}

#[test]
fn fasta_empty_input_yields_no_pages() {
    assert_eq!(run_fasta(b"", 4), b"");
}

#[test]
fn fasta_sequence_shorter_than_k_at_eof_discarded() {
    // The 3-base fragment is saved as overlap and dropped at EOF (< k).
    assert_eq!(run_fasta(b">s1\nACG\n", 4), b"");
}

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

#[test]
fn fasta_short_segment_before_ambiguous_emitted() {
    // "AC" (< k=4) before N is written with a separator — filtering by
    // length is deferred to the superkmer builder, not done here.
    assert_eq!(run_fasta(b">s1\nACNACGTACGT\n", 4), b"AC\x00ACGTACGT\x00");
}

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

// ── FastqParser ───────────────────────────────────────────────────────────

#[test]
fn fastq_single_record() {
    assert_eq!(
        run_fastq(b"@r1\nACGTACGT\n+\nIIIIIIII\n", 4),
        b"ACGTACGT\x00"
    );
}

#[test]
fn fastq_lowercase_uppercased() {
    assert_eq!(
        run_fastq(b"@r1\nacgtacgt\n+\nIIIIIIII\n", 4),
        b"ACGTACGT\x00"
    );
}

#[test]
fn fastq_quality_bytes_not_in_output() {
    // '@' (Phred 31 = ASCII 64) in quality must not appear in output.
    assert_eq!(
        run_fastq(b"@r1\nACGTACGT\n+\n@@@@@@@@\n", 4),
        b"ACGTACGT\x00"
    );
}

#[test]
fn fastq_two_records() {
    let data = b"@r1\nACGTACGT\n+\nIIIIIIII\n@r2\nTTTTTTTT\n+\nIIIIIIII\n";
    assert_eq!(run_fastq(data, 4), b"ACGTACGT\x00TTTTTTTT\x00");
}

#[test]
fn fastq_ambiguous_splits_sequence() {
    assert_eq!(
        run_fastq(b"@r1\nACGTNACGT\n+\nIIIIIIIII\n", 4),
        b"ACGT\x00ACGT\x00"
    );
}

#[test]
fn fastq_at_in_quality_line_not_a_record_start() {
    // '@' in the quality line must not trigger a new record parse.
    let data = b"@r1\nACGTACGT\n+\n@@@@@@@@\n@r2\nTTTTTTTT\n+\nIIIIIIII\n";
    assert_eq!(run_fastq(data, 4), b"ACGTACGT\x00TTTTTTTT\x00");
}

// ── GenbankParser ─────────────────────────────────────────────────────────

#[test]
fn genbank_origin_to_slash() {
    let data = b"LOCUS ...\nORIGIN\n        1 acgtacgt\n//\n";
    assert_eq!(run_genbank(data, 4), b"ACGTACGT\x00");
}

#[test]
fn genbank_position_numbers_and_spaces_skipped() {
    let data = b"ORIGIN\n        1 acgt acgt\n//\n";
    assert_eq!(run_genbank(data, 4), b"ACGTACGT\x00");
}

#[test]
fn genbank_two_records() {
    let data = b"ORIGIN\n        1 acgtacgt\n//\nLOCUS ...\nORIGIN\n        1 tttttttt\n//\n";
    assert_eq!(run_genbank(data, 4), b"ACGTACGT\x00TTTTTTTT\x00");
}

#[test]
fn genbank_ambiguous_splits_sequence() {
    let data = b"ORIGIN\n        1 acgtnacgt\n//\n";
    assert_eq!(run_genbank(data, 4), b"ACGT\x00ACGT\x00");
}

// ── NucPage ───────────────────────────────────────────────────────────────

#[test]
fn nuc_page_deref_correct_bytes() {
    let page = NucStream::<_, FastaParser>::new(Cursor::new(b">s1\nACGT\n".to_vec()), 4)
        .next()
        .expect("page");
    assert_eq!(page.deref(), b"ACGT\x00");
}

// ── NucPageCursor ─────────────────────────────────────────────────────────

fn make_page(data: &[u8], k: usize) -> NucPage {
    NucStream::<_, FastaParser>::new(Cursor::new(data.to_vec()), k)
        .next()
        .expect("at least one page")
}

#[test]
fn cursor_reads_bytes_in_order() {
    let page = make_page(b">s1\nACGTACGT\n", 4);
    let mut cur = page.cursor();
    assert_eq!(cur.next_byte(), Some(b'A'));
    assert_eq!(cur.next_byte(), Some(b'C'));
    assert_eq!(cur.next_byte(), Some(b'G'));
    assert_eq!(cur.next_byte(), Some(b'T'));
}

#[test]
fn cursor_rewind_rereads_bytes() {
    let page = make_page(b">s1\nACGTACGT\n", 4);
    let mut cur = page.cursor();
    cur.next_byte(); // A
    cur.next_byte(); // C
    cur.rewind(1);
    assert_eq!(cur.next_byte(), Some(b'C'));
    cur.rewind(2);
    assert_eq!(cur.next_byte(), Some(b'A'));
}

#[test]
fn cursor_returns_none_at_end() {
    // "ACGT\x00" = 5 bytes; consume all then expect None.
    let page = make_page(b">s1\nACGT\n", 4);
    let mut cur = page.cursor();
    for _ in 0..5 {
        cur.next_byte();
    }
    assert_eq!(cur.next_byte(), None);
}

#[test]
fn cursor_len_matches_page_content() {
    // "ACGTACGT\x00" = 9 bytes
    let page = make_page(b">s1\nACGTACGT\n", 4);
    let cur = page.cursor();
    assert_eq!(cur.len(), 9);
    assert!(!cur.is_empty());
}

// ── Overlap at page boundary ──────────────────────────────────────────────

#[test]
fn overlap_last_km1_bytes_prepended_to_next_page() {
    const K: usize = 11;
    // Sequence long enough to span two pages: PAGE_SIZE + K bytes.
    // Pattern chosen so boundary bytes are unambiguous.
    let seq: Vec<u8> = (0..PAGE_SIZE + K).map(|i| b"ACGT"[i % 4]).collect();
    let mut input = b">seq\n".to_vec();
    input.extend_from_slice(&seq);
    input.push(b'\n');

    let pages = pages_fasta(&input, K);
    assert!(pages.len() >= 2, "need at least two pages");

    let p1 = &pages[0];
    let p2 = &pages[1];

    // page1 must end with a \x00 separator (written by save_overlap)
    assert_eq!(*p1.last().unwrap(), 0x00, "page1 must end with separator");

    // last K-1 ACGT bytes of page1 == first K-1 bytes of page2
    let ol = K - 1;
    let p1_seq_end = &p1[p1.len() - 1 - ol..p1.len() - 1];
    let p2_start = &p2[..ol];
    assert_eq!(
        p1_seq_end, p2_start,
        "overlap bytes mismatch at page boundary"
    );
}

// ── Pool ──────────────────────────────────────────────────────────────────

#[test]
fn pool_buffer_reused_after_drop() {
    // Drop page1 so its buffer returns to the pool, then verify page2
    // is produced correctly (no corruption, no panic).
    const K: usize = 11;
    let seq: Vec<u8> = vec![b'A'; PAGE_SIZE + K];
    let mut input = b">seq\n".to_vec();
    input.extend_from_slice(&seq);
    input.push(b'\n');

    let mut stream = NucStream::<_, FastaParser>::new(Cursor::new(input), K);
    let page1 = stream.next().expect("page 1");
    assert!(!page1.deref().is_empty());
    drop(page1); // returns buffer to pool
    let page2 = stream.next().expect("page 2");
    assert!(!page2.deref().is_empty());
    // page2 must still start with A's (overlap from page1)
    assert_eq!(page2[0], b'A');
}
