use super::*;
use obikseq::{k, set_k, unitig::Unitig, Kmer};

// `obikseq::params` is process-wide (see obikseq/src/params.rs): tests in this
// file don't all use the same `k` (`push_palindrome_single_node` needs an
// even k=4 — no odd-length self-revcomp palindrome exists — while the rest
// use k=5), and they run concurrently by default. Serialize the
// set_k-through-use critical section across this file's tests so one test's
// `k` can never be overwritten mid-flight by another.
static K_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
fn lock_k() -> std::sync::MutexGuard<'static, ()> {
    K_LOCK.lock().unwrap_or_else(|e| e.into_inner())
}

// Build a graph from an ASCII sequence, inserting all canonical k-mers.
fn graph_from_ascii(seq: &[u8]) -> GraphDeBruijn {
    let mut g = GraphDeBruijn::new();
    let k = k();
    for i in 0..=seq.len().saturating_sub(k) {
        g.push(Kmer::from_ascii(&seq[i..i + k]).unwrap().canonical());
    }
    g
}

// Collect all canonical k-mers from an ASCII sequence into a sorted vec.
fn canonical_kmers(seq: &[u8]) -> Vec<CanonicalKmer> {
    let k = k();
    let mut v: Vec<CanonicalKmer> = (0..=seq.len().saturating_sub(k))
        .map(|i| Kmer::from_ascii(&seq[i..i + k]).unwrap().canonical())
        .collect();
    v.sort_unstable();
    v.dedup();
    v
}

fn collect_unitigs(g: &GraphDeBruijn) -> Vec<Unitig> {
    let mut unitigs = Vec::new();
    g.try_for_each_unitig(|unitig| -> Result<(), std::convert::Infallible> {
        unitigs.push(unitig.clone());
        Ok(())
    })
    .unwrap();
    unitigs
}

// ── push / canonicalisation ───────────────────────────────────────────────

#[test]
fn push_deduplicates_revcomp() {
    let k = 5;
    let _guard = lock_k();
    set_k(k);
    let kmer = Kmer::from_ascii(b"ACGTA").unwrap();
    let mut g = GraphDeBruijn::new();
    g.push(kmer.canonical());
    g.push(kmer.revcomp().canonical());
    assert_eq!(g.len(), 1, "kmer and its revcomp must map to the same node");
}

#[test]
fn push_palindrome_single_node() {
    // ACGT is its own revcomp
    let k = 4;
    let _guard = lock_k();
    set_k(k);
    let kmer = Kmer::from_ascii(b"ACGT").unwrap();
    assert_eq!(kmer, kmer.revcomp(), "test requires a palindrome");
    let mut g = GraphDeBruijn::new();
    g.push(kmer.canonical());
    assert_eq!(g.len(), 1);
}

// ── compute_degrees on a linear chain ────────────────────────────────────

// AAAAGGGG with k=5 → 4 distinct k-mers (AAAAG, AAAGG, AAGGG, AGGGG),
// clean linear chain, no Watson-Crick palindrome in first k-1 bases.
fn linear_chain_graph() -> (GraphDeBruijn, Vec<CanonicalKmer>) {
    let seq = b"AAAAGGGG";
    let g = graph_from_ascii(seq);
    let kmers = canonical_kmers(seq);
    (g, kmers)
}

#[test]
fn degrees_linear_chain_node_count() {
    let k = 5;
    let _guard = lock_k();
    set_k(k);
    let (g, kmers) = linear_chain_graph();
    assert_eq!(g.len(), kmers.len());
}

#[test]
fn degrees_linear_chain_extensions() {
    // A linear chain yields exactly 1 unitig covering all k-mers.
    // Note: start_iter must not be consumed standalone — its second pass only
    // finds true cycle nodes when interleaved with chain traversal (iter_unitig).
    let k = 5;
    let _guard = lock_k();
    set_k(k);
    let seq = b"AAAAGGGG";
    let g = graph_from_ascii(seq);
    g.compute_degrees_and_mark_starts();
    let unitigs: Vec<Unitig> = collect_unitigs(&g);
    assert_eq!(unitigs.len(), 1, "linear chain → exactly one unitig");
    // seql = k + (n_kmers - 1) = 5 + 3 = 8 = seq.len()
    assert_eq!(
        unitigs[0].seql(),
        seq.len(),
        "unitig spans the full sequence"
    );
    assert_eq!(
        kmers_from_unitigs(&unitigs),
        canonical_kmers(seq),
        "unitig k-mers must equal inserted k-mers"
    );
}

// ── unitig reconstruction ─────────────────────────────────────────────────

// Round-trip: all canonical k-mers in the unitigs == all canonical k-mers inserted.
fn kmers_from_unitigs(unitigs: &[Unitig]) -> Vec<CanonicalKmer> {
    let mut v: Vec<CanonicalKmer> = unitigs
        .iter()
        .flat_map(|u| u.iter_canonical_kmers())
        .collect();
    v.sort_unstable();
    v.dedup();
    v
}

#[test]
fn unitig_roundtrip_linear() {
    // Non-repetitive sequence: all k-mers must be recovered across unitigs.
    let k = 5;
    let _guard = lock_k();
    set_k(k);
    let seq = b"ACCTGGCTA";
    let g = graph_from_ascii(seq);
    g.compute_degrees_and_mark_starts();
    let unitigs: Vec<Unitig> = collect_unitigs(&g);
    assert!(!unitigs.is_empty());
    assert_eq!(
        kmers_from_unitigs(&unitigs),
        canonical_kmers(seq),
        "unitig must contain exactly the inserted k-mers"
    );
}

#[test]
fn unitig_roundtrip_longer_sequence() {
    // Longer non-repetitive sequence with no repeated k-mer of length k.
    // ACGTGGCTATCGAC with k=5 → 10 distinct k-mers, one linear chain.
    let k = 5;
    let _guard = lock_k();
    set_k(k);
    let seq = b"ACGTGGCTATCGAC";
    let g = graph_from_ascii(seq);
    g.compute_degrees_and_mark_starts();
    let unitigs: Vec<Unitig> = collect_unitigs(&g);
    let mut got = kmers_from_unitigs(&unitigs);
    let mut expected = canonical_kmers(seq);
    got.sort_unstable();
    expected.sort_unstable();
    assert_eq!(got, expected);
}

#[test]
fn unitig_isolated_node() {
    // Single k-mer with no neighbours
    let k = 5;
    let _guard = lock_k();
    set_k(k);
    let kmer = Kmer::from_ascii(b"ACGTA").unwrap();
    let mut g = GraphDeBruijn::new();
    g.push(kmer.canonical());
    g.compute_degrees_and_mark_starts();
    let unitigs: Vec<Unitig> = collect_unitigs(&g);
    assert_eq!(unitigs.len(), 1);
    assert_eq!(unitigs[0].seql(), k);
}

#[test]
fn unitig_two_isolated_nodes() {
    let k = 5;
    let _guard = lock_k();
    set_k(k);
    let mut g = GraphDeBruijn::new();
    // Two k-mers that share no (k-1)-overlap
    g.push(Kmer::from_ascii(b"AAAAA").unwrap().canonical());
    g.push(Kmer::from_ascii(b"TTTTT").unwrap().canonical()); // same canonical as AAAAA — dedup
                                                             // They collapse to one canonical node
    assert_eq!(g.len(), 1);
}

#[test]
fn unitig_two_truly_distinct_isolated_nodes() {
    let k = 5;
    let _guard = lock_k();
    set_k(k);
    let mut g = GraphDeBruijn::new();
    g.push(Kmer::from_ascii(b"AAAAC").unwrap().canonical());
    g.push(Kmer::from_ascii(b"GGGGT").unwrap().canonical());
    g.compute_degrees_and_mark_starts();
    let unitigs: Vec<Unitig> = collect_unitigs(&g);
    // Each isolated node → one unitig of length k
    assert_eq!(unitigs.len(), 2);
    assert!(unitigs.iter().all(|u| u.seql() == k));
}

// ── all k-mers covered, none duplicated ───────────────────────────────────

#[test]
fn no_kmer_lost_or_duplicated() {
    let k = 5;
    let _guard = lock_k();
    set_k(k);
    let seq = b"ACGTACGTACGTTTTTACGTACGT";
    let g = graph_from_ascii(seq);
    g.compute_degrees_and_mark_starts();
    let unitigs: Vec<Unitig> = collect_unitigs(&g);
    let got = kmers_from_unitigs(&unitigs);
    let expected = canonical_kmers(seq);
    assert_eq!(
        got.len(),
        expected.len(),
        "kmer count mismatch: got {}, expected {}",
        got.len(),
        expected.len()
    );
    assert_eq!(got, expected, "kmer sets differ");
}

// ── cycle coverage ────────────────────────────────────────────────────────

#[test]
fn cycle_kmers_not_lost() {
    // ACGTACGT with k=5 forms a pure cycle: ACGTA→CGTAC→GTACG→TACGT→ACGTA.
    // start_iter first pass yields nothing (all nodes internal); second pass
    // picks up cycle entries. All 4 k-mers must appear in the unitigs.
    let k = 5;
    let _guard = lock_k();
    set_k(k);
    let seq = b"ACGTACGT";
    let g = graph_from_ascii(seq);
    g.compute_degrees_and_mark_starts();
    let unitigs: Vec<Unitig> = collect_unitigs(&g);
    let got = kmers_from_unitigs(&unitigs);
    let expected = canonical_kmers(seq);
    assert_eq!(got.len(), expected.len(), "cycle k-mers lost");
    assert_eq!(got, expected);
}

// ── branching graph ───────────────────────────────────────────────────────
//
// Topology (k=5):  two sources A,B converge at C; chain C-D-E-F;
// F branches to G and H; H continues H-M-N; second source J feeds I-F.
// Every k-mer must appear in exactly one unitig (no duplication, no loss).
#[test]
fn branching_graph_no_kmer_lost_or_duplicated() {
    // Build sequences that realise the topology without accidental overlaps.
    // Each "node" is a distinct 5-mer; edges share a 4-mer suffix/prefix.
    // We use long non-repetitive sequences and extract only the required kmers.
    let k: usize = 5;
    let _guard = lock_k();
    set_k(k);
    let mut g = GraphDeBruijn::new();

    // Helper: insert all k-mers of a sequence.
    let mut insert = |seq: &[u8]| {
        for i in 0..=seq.len().saturating_sub(k) {
            g.push(Kmer::from_ascii(&seq[i..i + k]).unwrap().canonical());
        }
    };

    // Chains that realise the topology:
    //   A-C  (A→C share 4-mer overlap)
    //   B-C  (B→C share 4-mer overlap, different prefix)
    //   C-D-E-F
    //   F-G  (F→G)
    //   F-H-M-N  (F→H→M→N)
    //   J-I-F  (J→I→F)
    insert(b"AACGTGGCTA"); // A-C-D … part of the right branch
    insert(b"TACGTGGCTA"); // B-C-D … merges at C (same C-suffix)
    insert(b"CGTGGCTACG"); // continues D-E-F-G
    insert(b"CGTGGCTACC"); // F-H branch (different last base)
    insert(b"GTGGCTACCGT"); // H-M-N continuation
    insert(b"TTCGTGGCTA"); // J-I-F  (different J prefix)

    g.compute_degrees_and_mark_starts();
    let unitigs: Vec<Unitig> = collect_unitigs(&g);

    // Collect all k-mers from unitigs.
    let got = kmers_from_unitigs(&unitigs);

    // Collect all distinct canonical k-mers inserted.
    let mut expected: Vec<CanonicalKmer> = Vec::new();
    for seq in &[
        b"AACGTGGCTA".as_slice(),
        b"TACGTGGCTA",
        b"CGTGGCTACG",
        b"CGTGGCTACC",
        b"GTGGCTACCGT",
        b"TTCGTGGCTA",
    ] {
        expected.extend(canonical_kmers(seq));
    }
    expected.sort_unstable();
    expected.dedup();

    assert_eq!(
        got.len(),
        expected.len(),
        "k-mer count mismatch: got {}, expected {}",
        got.len(),
        expected.len()
    );
    assert_eq!(got, expected, "k-mer sets differ");
}
