use tempfile::tempdir;

use crate::{PersistentBitVec, PersistentBitVecBuilder, PersistentCompactIntVec, PersistentCompactIntVecBuilder};

fn make_bv(bits: &[bool]) -> (tempfile::TempDir, PersistentBitVec) {
    let dir  = tempdir().unwrap();
    let path = dir.path().join("v.pbiv");
    let mut b = PersistentBitVecBuilder::new(bits.len(), &path).unwrap();
    for (i, &v) in bits.iter().enumerate() {
        b.set(i, v);
    }
    b.close().unwrap();
    let r = PersistentBitVec::open(&path).unwrap();
    (dir, r)
}

#[test]
fn all_false_by_default() {
    let dir  = tempdir().unwrap();
    let path = dir.path().join("v.pbiv");
    let b = PersistentBitVecBuilder::new(16, &path).unwrap();
    b.close().unwrap();
    let r = PersistentBitVec::open(&path).unwrap();
    assert!(r.iter().all(|v| !v));
    assert_eq!(r.count_ones(), 0);
}

#[test]
fn set_get_roundtrip() {
    let bits = vec![true, false, true, true, false, false, true, false, true];
    let (_dir, r) = make_bv(&bits);
    for (i, &expected) in bits.iter().enumerate() {
        assert_eq!(r.get(i), expected, "slot {i}");
    }
}

#[test]
fn iter_matches_get() {
    let bits = vec![true, false, true, false, true, true, false, true, false, true];
    let (_dir, r) = make_bv(&bits);
    let via_iter: Vec<bool> = r.iter().collect();
    let via_get:  Vec<bool> = (0..bits.len()).map(|s| r.get(s)).collect();
    assert_eq!(via_iter, via_get);
}

#[test]
fn count_ones_and_zeros() {
    let bits = vec![true, false, true, true, false];
    let (_dir, r) = make_bv(&bits);
    assert_eq!(r.count_ones(),  3);
    assert_eq!(r.count_zeros(), 2);
}

#[test]
fn non_byte_aligned_length() {
    // 13 bits — last byte has 5 padding zeros
    let bits: Vec<bool> = (0..13).map(|i| i % 3 == 0).collect();
    let (_dir, r) = make_bv(&bits);
    assert_eq!(r.iter().collect::<Vec<_>>(), bits);
}

#[test]
fn build_from_roundtrip() {
    let bits = vec![true, false, true, false, true, true];
    let (_da, ra) = make_bv(&bits);
    let dir_b = tempdir().unwrap();
    let path_b = dir_b.path().join("b.pbiv");
    PersistentBitVecBuilder::build_from(&ra, &path_b).unwrap().close().unwrap();
    let rb = PersistentBitVec::open(&path_b).unwrap();
    assert_eq!(ra.iter().collect::<Vec<_>>(), rb.iter().collect::<Vec<_>>());
}

#[test]
fn op_and() {
    let (_da, ra) = make_bv(&[true,  true,  false, false]);
    let (_db, rb) = make_bv(&[true,  false, true,  false]);
    let dir = tempdir().unwrap();
    let path = dir.path().join("out.pbiv");
    let mut b = PersistentBitVecBuilder::build_from(&ra, &path).unwrap();
    b.and(rb.view());
    b.close().unwrap();
    let r = PersistentBitVec::open(&path).unwrap();
    assert_eq!(r.iter().collect::<Vec<_>>(), vec![true, false, false, false]);
}

#[test]
fn op_or() {
    let (_da, ra) = make_bv(&[true,  true,  false, false]);
    let (_db, rb) = make_bv(&[true,  false, true,  false]);
    let dir = tempdir().unwrap();
    let path = dir.path().join("out.pbiv");
    let mut b = PersistentBitVecBuilder::build_from(&ra, &path).unwrap();
    b.or(rb.view());
    b.close().unwrap();
    let r = PersistentBitVec::open(&path).unwrap();
    assert_eq!(r.iter().collect::<Vec<_>>(), vec![true, true, true, false]);
}

#[test]
fn op_xor() {
    let (_da, ra) = make_bv(&[true,  true,  false, false]);
    let (_db, rb) = make_bv(&[true,  false, true,  false]);
    let dir = tempdir().unwrap();
    let path = dir.path().join("out.pbiv");
    let mut b = PersistentBitVecBuilder::build_from(&ra, &path).unwrap();
    b.xor(rb.view());
    b.close().unwrap();
    let r = PersistentBitVec::open(&path).unwrap();
    assert_eq!(r.iter().collect::<Vec<_>>(), vec![false, true, true, false]);
}

#[test]
fn op_not_byte_aligned() {
    let bits = vec![true, false, true, false, true, false, false, true]; // 8 bits, no padding
    let (_da, ra) = make_bv(&bits);
    let dir = tempdir().unwrap();
    let path = dir.path().join("out.pbiv");
    let mut b = PersistentBitVecBuilder::build_from(&ra, &path).unwrap();
    b.not();
    b.close().unwrap();
    let r = PersistentBitVec::open(&path).unwrap();
    let expected: Vec<bool> = bits.iter().map(|&v| !v).collect();
    assert_eq!(r.iter().collect::<Vec<_>>(), expected);
}

#[test]
fn op_not_non_aligned_no_padding_leak() {
    // 5 bits: [T,F,T,F,T] → NOT → [F,T,F,T,F]; padding bits must stay 0
    let bits = vec![true, false, true, false, true];
    let (_da, ra) = make_bv(&bits);
    let dir = tempdir().unwrap();
    let path = dir.path().join("out.pbiv");
    let mut b = PersistentBitVecBuilder::build_from(&ra, &path).unwrap();
    b.not();
    b.close().unwrap();
    let r = PersistentBitVec::open(&path).unwrap();
    assert_eq!(r.iter().collect::<Vec<_>>(), vec![false, true, false, true, false]);
    // count_ones must reflect exactly the 2 set bits, not 2 + stray padding bits
    assert_eq!(r.count_ones(), 2);
}

#[test]
fn jaccard_dist_basic() {
    // A={0,2} B={0,1} → inter=1 union=3 → dist=2/3
    let (_da, ra) = make_bv(&[true,  false, true,  false]);
    let (_db, rb) = make_bv(&[true,  true,  false, false]);
    let d = ra.jaccard_dist(&rb);
    assert!((d - 2.0 / 3.0).abs() < 1e-12, "got {d}");
}

#[test]
fn jaccard_dist_identical() {
    let (_d, r) = make_bv(&[true, false, true]);
    assert_eq!(r.jaccard_dist(&r), 0.0);
}

#[test]
fn jaccard_dist_disjoint() {
    let (_da, ra) = make_bv(&[true,  false]);
    let (_db, rb) = make_bv(&[false, true]);
    assert_eq!(ra.jaccard_dist(&rb), 1.0);
}

fn make_pciv(counts: &[u32]) -> (tempfile::TempDir, PersistentCompactIntVec) {
    let dir  = tempdir().unwrap();
    let path = dir.path().join("c.pciv");
    let mut b = PersistentCompactIntVecBuilder::new(counts.len(), &path).unwrap();
    for (i, &v) in counts.iter().enumerate() { b.set(i, v); }
    b.close().unwrap();
    let r = PersistentCompactIntVec::open(&path).unwrap();
    (dir, r)
}

#[test]
fn build_from_presence_basic() {
    let counts = vec![0u32, 3, 0, 7, 1, 0];
    let (_dc, rc) = make_pciv(&counts);
    let dir  = tempdir().unwrap();
    let path = dir.path().join("bv.pbiv");
    PersistentBitVecBuilder::build_from_presence(&rc, &path).unwrap().close().unwrap();
    let bv = PersistentBitVec::open(&path).unwrap();
    assert_eq!(bv.iter().collect::<Vec<_>>(), vec![false, true, false, true, true, false]);
    assert_eq!(bv.count_ones(), 3);
}

#[test]
fn build_from_counts_with_threshold() {
    // threshold=5: present if count >= 5
    let counts = vec![0u32, 3, 5, 10, 4, 6];
    let (_dc, rc) = make_pciv(&counts);
    let dir  = tempdir().unwrap();
    let path = dir.path().join("bv.pbiv");
    PersistentBitVecBuilder::build_from_counts(&rc, 5, &path).unwrap().close().unwrap();
    let bv = PersistentBitVec::open(&path).unwrap();
    assert_eq!(bv.iter().collect::<Vec<_>>(), vec![false, false, true, true, false, true]);
}

#[test]
fn build_from_counts_with_overflow_values() {
    // overflow value (>= 255) must pass threshold
    let counts = vec![0u32, 100, 1_000_000, 50];
    let (_dc, rc) = make_pciv(&counts);
    let dir  = tempdir().unwrap();
    let path = dir.path().join("bv.pbiv");
    PersistentBitVecBuilder::build_from_counts(&rc, 200, &path).unwrap().close().unwrap();
    let bv = PersistentBitVec::open(&path).unwrap();
    assert_eq!(bv.iter().collect::<Vec<_>>(), vec![false, false, true, false]);
}

#[test]
fn hamming_dist_basic() {
    // differ at positions 1 and 2
    let (_da, ra) = make_bv(&[true,  true,  false, true]);
    let (_db, rb) = make_bv(&[true,  false, true,  true]);
    assert_eq!(ra.hamming_dist(&rb), 2);
}
