use super::*;

#[cfg(test)]
mod tests {
    use super::*;

    // Tests use ConstLen<N> — no dependency on global params singletons.
    type K1 = KmerOf<ConstLen<1>>;
    type K4 = KmerOf<ConstLen<4>>;

    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 make_seq<const N: usize>() -> Vec<u8> {
        (0..N).map(|i| b"ACGT"[i % 4]).collect()
    }

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

    #[test]
    fn ascii_roundtrip() {
        macro_rules! check {
            ($n:expr) => {{
                let ascii = make_seq::<$n>();
                let kmer = KmerOf::<ConstLen<$n>>::from_ascii(&ascii).unwrap();
                assert_eq!(kmer.to_ascii(), ascii, "roundtrip failed for k={}", $n);
            }};
        }
        check!(1);
        check!(2);
        check!(3);
        check!(4);
        check!(8);
        check!(11);
        check!(16);
        check!(31);
        check!(32);
    }

    #[test]
    fn from_ascii_all_bases() {
        for (base, expected) in [(b'A', b'A'), (b'C', b'C'), (b'G', b'G'), (b'T', b'T')] {
            let kmer = K1::from_ascii(&[base]).unwrap();
            assert_eq!(kmer.to_ascii(), vec![expected]);
        }
    }

    #[test]
    fn from_ascii_invalid_k() {
        assert!(KmerOf::<ConstLen<0>>::from_ascii(b"A").is_err());
        assert!(KmerOf::<ConstLen<33>>::from_ascii(b"ACGT").is_err());
    }

    #[test]
    fn from_ascii_too_short() {
        assert!(KmerOf::<ConstLen<4>>::from_ascii(b"ACG").is_err());
    }

    // ── nucleotide ────────────────────────────────────────────────────────────

    #[test]
    fn nucleotide_extraction() {
        let kmer = K4::from_ascii(b"ACGT").unwrap();
        assert_eq!(kmer.nucleotide(0), 0b00); // A
        assert_eq!(kmer.nucleotide(1), 0b01); // C
        assert_eq!(kmer.nucleotide(2), 0b10); // G
        assert_eq!(kmer.nucleotide(3), 0b11); // T
    }

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

    #[test]
    fn revcomp_known_values() {
        let cases: &[(&[u8], &[u8])] = &[
            (b"A", b"T"),
            (b"AC", b"GT"),
            (b"ACG", b"CGT"),
            (b"ACGT", b"ACGT"),
            (b"AAAA", b"TTTT"),
            (b"TTTT", b"AAAA"),
        ];
        for (seq, expected) in cases {
            macro_rules! check_len {
                ($n:expr) => {
                    if seq.len() == $n {
                        let kmer = KmerOf::<ConstLen<$n>>::from_ascii(seq).unwrap();
                        assert_eq!(
                            kmer.revcomp().to_ascii(),
                            *expected,
                            "revcomp wrong for \"{}\"",
                            std::str::from_utf8(seq).unwrap()
                        );
                    }
                };
            }
            check_len!(1);
            check_len!(2);
            check_len!(3);
            check_len!(4);
        }
    }

    #[test]
    fn revcomp_vs_reference() {
        macro_rules! check {
            ($n:expr) => {{
                let ascii = make_seq::<$n>();
                let expected = ascii_revcomp(&ascii);
                let rc = KmerOf::<ConstLen<$n>>::from_ascii(&ascii)
                    .unwrap()
                    .revcomp();
                assert_eq!(rc.to_ascii(), expected, "revcomp wrong for k={}", $n);
            }};
        }
        check!(1);
        check!(4);
        check!(8);
        check!(11);
        check!(16);
        check!(31);
        check!(32);
    }

    #[test]
    fn revcomp_involution() {
        macro_rules! check {
            ($n:expr) => {{
                let ascii = make_seq::<$n>();
                let kmer = KmerOf::<ConstLen<$n>>::from_ascii(&ascii).unwrap();
                assert_eq!(
                    kmer.revcomp().revcomp(),
                    kmer,
                    "revcomp∘revcomp≠id for k={}",
                    $n
                );
            }};
        }
        check!(1);
        check!(4);
        check!(8);
        check!(16);
        check!(31);
        check!(32);
    }

    // ── canonical ─────────────────────────────────────────────────────────────

    #[test]
    fn canonical_palindrome() {
        let kmer = K4::from_ascii(b"ACGT").unwrap();
        assert_eq!(kmer.canonical().into_kmer(), kmer);
    }

    #[test]
    fn canonical_chooses_lesser() {
        let kmer = K4::from_ascii(b"TTTT").unwrap();
        let expected = K4::from_ascii(b"AAAA").unwrap();
        assert_eq!(kmer.canonical().into_kmer(), expected);
    }

    #[test]
    fn canonical_is_minimal() {
        macro_rules! check {
            ($n:expr) => {{
                let ascii = make_seq::<$n>();
                let ck = KmerOf::<ConstLen<$n>>::from_ascii(&ascii)
                    .unwrap()
                    .canonical();
                let rc = ck.revcomp();
                assert!(ck.raw() <= rc.raw(), "canonical not minimal for k={}", $n);
            }};
        }
        check!(1);
        check!(4);
        check!(8);
        check!(16);
        check!(31);
        check!(32);
    }

    #[test]
    fn canonical_idempotent() {
        macro_rules! check {
            ($n:expr) => {{
                let ck = KmerOf::<ConstLen<$n>>::from_ascii(&make_seq::<$n>())
                    .unwrap()
                    .canonical();
                assert_eq!(
                    ck.into_kmer().canonical(),
                    ck,
                    "canonical not idempotent for k={}",
                    $n
                );
            }};
        }
        check!(1);
        check!(4);
        check!(8);
        check!(16);
        check!(31);
        check!(32);
    }

    // ── central_canonical_neighbors ─────────────────────────────────────────

    #[test]
    fn central_canonical_neighbors_hand_checked_k3() {
        // k=3, centre = index 1. For "ACG", every one of the 4 central
        // substitutions ("AAG","ACG","AGG","ATG") happens to stay in forward
        // orientation when canonicalised (verified by hand: each is already
        // lexicographically <= its own reverse complement), so this case
        // exercises the substitution logic without the RC-flip edge case.
        let ck = KmerOf::<ConstLen<3>>::from_ascii(b"ACG").unwrap().canonical();
        let neighbours = ck.central_canonical_neighbors();
        let ascii: Vec<Vec<u8>> = neighbours.iter().map(|n| n.to_ascii()).collect();
        assert_eq!(ascii, vec![b"AAG".to_vec(), b"ACG".to_vec(), b"AGG".to_vec(), b"ATG".to_vec()]);
        // The identity substitution (centre unchanged) must reproduce `ck`.
        assert!(neighbours.contains(&ck));
    }

    #[test]
    fn central_canonical_neighbors_identity_present_for_various_k() {
        macro_rules! check {
            ($n:expr) => {{
                let ck = KmerOf::<ConstLen<$n>>::from_ascii(&make_seq::<$n>())
                    .unwrap()
                    .canonical();
                let neighbours = ck.central_canonical_neighbors();
                assert!(
                    neighbours.contains(&ck),
                    "identity substitution missing from central_canonical_neighbors for k={}",
                    $n
                );
                // Every returned neighbour must itself already be canonical.
                for n in &neighbours {
                    assert_eq!(n.into_kmer().canonical(), *n, "neighbour not canonical for k={}", $n);
                }
            }};
        }
        check!(1);
        check!(3);
        check!(5);
        check!(31);
    }
}
