// ── tests ─────────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use crate::packed_seq::PackedSeq as Unitig;
    use crate::set_k;

    fn make_seq(len: usize) -> Vec<u8> {
        (0..len).map(|i| b"ACGT"[i % 4]).collect()
    }

    fn ascii_revcomp(seq: &[u8]) -> Vec<u8> {
        seq.iter()
            .rev()
            .map(|&b| match b {
                b'A' => b'T',
                b'T' => b'A',
                b'C' => b'G',
                b'G' => b'C',
                _ => b'A',
            })
            .collect()
    }

    fn test_lengths() -> impl Iterator<Item = usize> {
        (1..=9).chain([255, 256, 257, 1000, 10_000])
    }

    // ── from_ascii / to_ascii ─────────────────────────────────────────────────

    #[test]
    fn ascii_roundtrip_all_lengths() {
        for len in test_lengths() {
            let ascii = make_seq(len);
            let u = Unitig::from_ascii(&ascii);
            assert_eq!(u.to_ascii(), ascii, "roundtrip failed for len={len}");
        }
    }

    // ── seql ──────────────────────────────────────────────────────────────────

    #[test]
    fn seql_roundtrip() {
        for len in test_lengths() {
            let u = Unitig::from_ascii(&make_seq(len));
            assert_eq!(u.seql(), len);
        }
    }

    // ── revcomp ───────────────────────────────────────────────────────────────

    #[test]
    fn revcomp_known_values() {
        let cases = [
            ("A", "T"),
            ("AC", "GT"),
            ("ACG", "CGT"),
            ("ACGT", "ACGT"),
            ("ACGTA", "TACGT"),
        ];
        for (seq, expected) in cases {
            let mut u = Unitig::from_ascii(seq.as_bytes());
            u.revcomp_inplace();
            assert_eq!(
                u.to_ascii(),
                expected.as_bytes(),
                "revcomp wrong for \"{seq}\""
            );
        }
    }

    #[test]
    fn revcomp_vs_reference_all_lengths() {
        for len in test_lengths() {
            let ascii = make_seq(len);
            let expected = ascii_revcomp(&ascii);
            let mut u = Unitig::from_ascii(&ascii);
            u.revcomp_inplace();
            assert_eq!(u.to_ascii(), expected, "revcomp wrong for len={len}");
        }
    }

    #[test]
    fn revcomp_involution_all_lengths() {
        for len in test_lengths() {
            let ascii = make_seq(len);
            let mut u = Unitig::from_ascii(&ascii);
            u.revcomp_inplace();
            u.revcomp_inplace();
            assert_eq!(u.to_ascii(), ascii, "revcomp∘revcomp≠id for len={len}");
        }
    }

    // ── canonicalize ──────────────────────────────────────────────────────────

    #[test]
    fn canonical_palindrome_unchanged() {
        let mut u = Unitig::from_ascii(b"ACGT");
        u.canonicalize();
        assert_eq!(u.to_ascii(), b"ACGT");
    }

    #[test]
    fn canonical_chooses_revcomp() {
        let mut u = Unitig::from_ascii(b"TTTT");
        u.canonicalize();
        assert_eq!(u.to_ascii(), b"AAAA");
    }

    #[test]
    fn canonical_is_minimal_all_lengths() {
        for len in test_lengths() {
            let ascii = make_seq(len);
            let mut u = Unitig::from_ascii(&ascii);
            u.canonicalize();
            let fwd = u.to_ascii();
            let rev = ascii_revcomp(&fwd);
            assert!(fwd <= rev, "canonical not minimal for len={len}");
        }
    }

    // ── kmer extraction ───────────────────────────────────────────────────────

    #[test]
    fn kmer_all_positions() {
        set_k(4);
        let k = crate::params::k();
        let ascii = b"ACGTACGTACGT";
        let u = Unitig::from_ascii(ascii);
        for i in 0..=ascii.len() - k {
            let kmer = u.kmer(i).unwrap();
            let expected = crate::kmer::Kmer::from_ascii(&ascii[i..i + k]).unwrap();
            assert_eq!(kmer, expected, "mismatch at position {i}");
        }
    }

    // ── iter_kmers ────────────────────────────────────────────────────────────

    #[test]
    fn iter_kmers_matches_kmer_at_each_position() {
        set_k(4);
        let ascii = make_seq(20);
        let u = Unitig::from_ascii(&ascii);
        let kmers: Vec<crate::kmer::Kmer> = u.iter_kmers().collect();
        for (i, &km) in kmers.iter().enumerate() {
            assert_eq!(km, u.kmer(i).unwrap(), "pos={i}");
        }
    }

    #[test]
    fn iter_kmers_long_unitig() {
        set_k(4);
        let k = crate::params::k();
        let ascii = make_seq(10_000);
        let u = Unitig::from_ascii(&ascii);
        assert_eq!(u.iter_kmers().count(), 10_000 - k + 1);
    }

    // ── binary serialisation ──────────────────────────────────────────────────

    #[test]
    fn binary_roundtrip_all_lengths() {
        // write_to_binary encodes a single chunk: seql must be in [k, MAX_KMERS_PER_CHUNK+k-1].
        use crate::packed_seq::MAX_KMERS_PER_CHUNK;
        set_k(4);
        let k = crate::params::k();
        let valid_lengths: Vec<usize> = (k..=9).chain([255, 256, 257, MAX_KMERS_PER_CHUNK + k - 1]).collect();
        for len in valid_lengths {
            let u = Unitig::from_ascii(&make_seq(len));
            let mut buf = Vec::new();
            u.write_to_binary(&mut buf).unwrap();
            let u2 = Unitig::read_from_binary(&mut buf.as_slice()).unwrap();
            assert_eq!(u, u2, "binary roundtrip failed for len={len}");
        }
    }
}
