use tempfile::tempdir;

use crate::{PersistentBitMatrix, PersistentBitMatrixBuilder};

fn make_matrix(cols: &[&[bool]]) -> (tempfile::TempDir, PersistentBitMatrix) {
    let n = cols.first().map_or(0, |c| c.len());
    let dir = tempdir().unwrap();
    let presence = dir.path().join("presence");
    let mut b = PersistentBitMatrixBuilder::new(n, &presence).unwrap();
    for &col in cols {
        let mut cb = b.add_col().unwrap();
        for (slot, &v) in col.iter().enumerate() {
            cb.set(slot, v);
        }
        cb.close().unwrap();
    }
    b.close().unwrap();
    let m = PersistentBitMatrix::open(dir.path()).unwrap();
    (dir, m)
}

#[test]
fn single_col_roundtrip() {
    let dir = tempdir().unwrap();
    let presence = dir.path().join("presence");
    let mut b = PersistentBitMatrixBuilder::new(4, &presence).unwrap();
    let mut col = b.add_col().unwrap();
    col.set(0, true);
    col.set(1, false);
    col.set(2, true);
    col.set(3, true);
    col.close().unwrap();
    b.close().unwrap();

    let m = PersistentBitMatrix::open(dir.path()).unwrap();
    assert_eq!(m.n_cols(), 1);
    assert_eq!(m.n(), 4);
    assert_eq!(&*m.row(0), &[true]);
    assert_eq!(&*m.row(1), &[false]);
    assert_eq!(&*m.row(2), &[true]);
    assert_eq!(&*m.row(3), &[true]);
}

#[test]
fn two_cols_roundtrip() {
    let dir = tempdir().unwrap();
    let presence = dir.path().join("presence");
    let mut b = PersistentBitMatrixBuilder::new(3, &presence).unwrap();
    let mut col0 = b.add_col().unwrap();
    col0.set(0, true); col0.set(1, false); col0.set(2, true);
    col0.close().unwrap();
    let mut col1 = b.add_col().unwrap();
    col1.set(0, false); col1.set(1, true); col1.set(2, false);
    col1.close().unwrap();
    b.close().unwrap();

    let m = PersistentBitMatrix::open(dir.path()).unwrap();
    assert_eq!(m.n_cols(), 2);
    assert_eq!(&*m.row(0), &[true, false]);
    assert_eq!(&*m.row(1), &[false, true]);
    assert_eq!(&*m.row(2), &[true, false]);
}

#[test]
fn col_accessor() {
    let dir = tempdir().unwrap();
    let presence = dir.path().join("presence");
    let mut b = PersistentBitMatrixBuilder::new(3, &presence).unwrap();
    let mut col = b.add_col().unwrap();
    col.set(0, true); col.set(1, false); col.set(2, true);
    col.close().unwrap();
    b.close().unwrap();

    let m = PersistentBitMatrix::open(dir.path()).unwrap();
    assert!(m.col(0).get(0));
    assert!(!m.col(0).get(1));
    assert!(m.col(0).get(2));
}

#[test]
fn zero_cols_roundtrip() {
    let dir = tempdir().unwrap();
    let presence = dir.path().join("presence");
    let b = PersistentBitMatrixBuilder::new(8, &presence).unwrap();
    b.close().unwrap();

    let m = PersistentBitMatrix::open(dir.path()).unwrap();
    assert_eq!(m.n_cols(), 0);
    assert_eq!(m.n(), 8);
}

// ── count_ones ────────────────────────────────────────────────────────────────

#[test]
fn count_ones_per_column() {
    let (_d, m) = make_matrix(&[
        &[true,  false, true,  true],
        &[false, false, false, false],
        &[true,  true,  true,  false],
    ]);
    let c = m.count_ones();
    assert_eq!(c[0], 3);
    assert_eq!(c[1], 0);
    assert_eq!(c[2], 3);
}

// ── Distance matrix tests ─────────────────────────────────────────────────────

#[test]
fn jaccard_dist_matrix_symmetry_and_diagonal() {
    let (_d, m) = make_matrix(&[
        &[true, false, true],
        &[true, true,  false],
        &[false, true, true],
    ]);
    let dm = m.jaccard_dist_matrix();
    let n = m.n_cols();
    for i in 0..n { assert_eq!(dm[[i, i]], 0.0, "diagonal"); }
    for i in 0..n {
        for j in 0..n {
            assert!((dm[[i, j]] - dm[[j, i]]).abs() < 1e-12, "symmetry [{i},{j}]");
        }
    }
}

#[test]
fn jaccard_dist_matrix_values_match_pairwise() {
    let (_d, m) = make_matrix(&[
        &[true, false, true],
        &[true, true,  false],
        &[false, true, true],
    ]);
    let dm = m.jaccard_dist_matrix();
    for i in 0..m.n_cols() {
        for j in 0..m.n_cols() {
            let expected = m.col(i).jaccard_dist(m.col(j));
            assert!((dm[[i, j]] - expected).abs() < 1e-12, "[{i},{j}]");
        }
    }
}

#[test]
fn hamming_dist_matrix_symmetry_and_diagonal() {
    let (_d, m) = make_matrix(&[
        &[true, false, true],
        &[true, true,  false],
        &[false, true, true],
    ]);
    let dm = m.hamming_dist_matrix();
    let n = m.n_cols();
    for i in 0..n { assert_eq!(dm[[i, i]], 0, "diagonal"); }
    for i in 0..n {
        for j in 0..n {
            assert_eq!(dm[[i, j]], dm[[j, i]], "symmetry [{i},{j}]");
        }
    }
}

#[test]
fn hamming_dist_matrix_values_match_pairwise() {
    let (_d, m) = make_matrix(&[
        &[true, false, true],
        &[true, true,  false],
        &[false, true, true],
    ]);
    let dm = m.hamming_dist_matrix();
    for i in 0..m.n_cols() {
        for j in 0..m.n_cols() {
            assert_eq!(dm[[i, j]], m.col(i).hamming_dist(m.col(j)), "[{i},{j}]");
        }
    }
}

#[test]
fn partial_jaccard_consistent() {
    let (_d, m) = make_matrix(&[
        &[true, false, true, true],
        &[true, true,  false, true],
        &[false, true, true, false],
    ]);
    let (inter, union) = m.partial_jaccard_dist_matrix();
    let n = m.n_cols();
    for i in 0..n {
        for j in i + 1..n {
            let (ei, eu) = m.col(i).partial_jaccard_dist(m.col(j));
            assert_eq!(inter[[i, j]], ei, "inter [{i},{j}]");
            assert_eq!(union[[i, j]], eu, "union [{i},{j}]");
            assert_eq!(inter[[j, i]], ei, "inter symmetry");
            assert_eq!(union[[j, i]], eu, "union symmetry");
        }
    }
}

#[test]
fn partial_hamming_matches_hamming() {
    let (_d, m) = make_matrix(&[
        &[true, false, true],
        &[false, true, true],
        &[true, true, false],
    ]);
    let partial = m.partial_hamming_dist_matrix();
    let full    = m.hamming_dist_matrix();
    assert_eq!(partial, full);
}
