use super::*;
use crate::reader::SKFileReader;
use obikseq::{SuperKmer, set_k};
use tempfile::{NamedTempFile, TempDir};

const TEST_K: usize = 4;

fn make_sk(seed: usize) -> SuperKmer {
    let bases: Vec<u8> = (0..8).map(|j| b"ACGT"[(seed + j) % 4]).collect();
    SuperKmer::from_ascii(&bases)
}

fn pool(max_open: usize) -> SharedPool {
    Arc::new(Mutex::new(SKFilePool::new(max_open)))
}

fn open_token(t: &mut SKFileWriter, sk: &SuperKmer) {
    t.set_flush_threshold(1);
    t.write(sk).unwrap();
}

#[test]
fn creation_holds_no_fd() {
    set_k(TEST_K);
    let dir = TempDir::new().unwrap();
    let p = pool(3);
    for i in 0..10 {
        create_token(&p, dir.path().join(format!("p{i}.zst"))).unwrap();
    }
    assert_eq!(p.lock().unwrap().open_count(), 0);
}

#[test]
fn pool_limits_open_fds() {
    set_k(TEST_K);
    let dir = TempDir::new().unwrap();
    let p = pool(3);
    let sk = make_sk(0);

    let mut tokens: Vec<SKFileWriter> = (0..6)
        .map(|i| create_token(&p, dir.path().join(format!("p{i}.zst"))).unwrap())
        .collect();

    for t in tokens.iter_mut() {
        open_token(t, &sk);
    }

    assert!(
        p.lock().unwrap().open_count() <= 3,
        "open={}",
        p.lock().unwrap().open_count()
    );
}

#[test]
fn evicted_token_stays_logically_open() {
    set_k(TEST_K);
    let dir = TempDir::new().unwrap();
    let p = pool(1);
    let sk = make_sk(0);

    let mut t0 = create_token(&p, dir.path().join("a.zst")).unwrap();
    let mut t1 = create_token(&p, dir.path().join("b.zst")).unwrap();

    open_token(&mut t0, &sk);
    open_token(&mut t1, &sk);

    assert!(t0.is_open(), "t0 must remain logically open after eviction");
    assert_eq!(p.lock().unwrap().open_count(), 1);
}

#[test]
fn evicted_data_readable_after_close_all() {
    set_k(TEST_K);
    let dir = TempDir::new().unwrap();
    let p = pool(1);
    let sk = make_sk(0);

    let mut t0 = create_token(&p, dir.path().join("a.zst")).unwrap();
    let mut t1 = create_token(&p, dir.path().join("b.zst")).unwrap();

    t0.set_flush_threshold(1);
    t0.write(&sk).unwrap();
    t1.set_flush_threshold(1);
    t1.write(&sk).unwrap();

    drop(t0);
    drop(t1);
    p.lock().unwrap().close_all().unwrap();

    for name in &["a.zst", "b.zst"] {
        let mut r = SKFileReader::open(dir.path().join(name)).unwrap();
        let got = r.read_batch(10).unwrap();
        assert_eq!(got.len(), 1, "{name}: expected 1 record");
    }
}

#[test]
fn touch_moves_to_mru_so_lru_is_evicted() {
    set_k(TEST_K);
    let dir = TempDir::new().unwrap();
    let p = pool(2);
    let sk = make_sk(0);

    let mut t0 = create_token(&p, dir.path().join("a.zst")).unwrap();
    let mut t1 = create_token(&p, dir.path().join("b.zst")).unwrap();
    let mut t2 = create_token(&p, dir.path().join("c.zst")).unwrap();

    open_token(&mut t0, &sk);
    open_token(&mut t1, &sk);

    t0.set_flush_threshold(1);
    t0.write(&sk).unwrap();

    open_token(&mut t2, &sk);

    let open_count = p.lock().unwrap().open_count();
    assert!(open_count <= 2, "open_count={open_count}");
}

#[test]
fn close_all_produces_readable_files() {
    set_k(TEST_K);
    let dir = TempDir::new().unwrap();
    let p = pool(8);
    let paths: Vec<_> = (0..4)
        .map(|i| dir.path().join(format!("{i}.zst")))
        .collect();

    let mut tokens: Vec<SKFileWriter> = paths
        .iter()
        .map(|path| create_token(&p, path.clone()).unwrap())
        .collect();

    for (i, t) in tokens.iter_mut().enumerate() {
        t.write(&make_sk(i)).unwrap();
    }
    for t in tokens.iter_mut() {
        t.close().unwrap();
    }
    p.lock().unwrap().close_all().unwrap();

    for path in &paths {
        let mut r = SKFileReader::open(path).unwrap();
        let got = r.read_batch(10).unwrap();
        assert_eq!(got.len(), 1);
    }
}

#[test]
fn write_batch_roundtrip() {
    set_k(TEST_K);
    let dir = TempDir::new().unwrap();
    let p = pool(4);
    let sks: Vec<_> = (0..50).map(make_sk).collect();
    let path = dir.path().join("batch.zst");

    let mut t = create_token(&p, path.clone()).unwrap();
    t.write_batch(&sks).unwrap();
    t.close().unwrap();

    let mut r = SKFileReader::open(&path).unwrap();
    let got = r.read_batch(100).unwrap();
    assert_eq!(got.len(), 50);
    for (a, b) in sks.iter().zip(got.iter()) {
        assert_eq!(a.to_ascii(), b.to_ascii());
    }
}

#[test]
fn from_system_limits_bounded() {
    set_k(TEST_K);
    let pool = SKFilePool::from_system_limits();
    assert!(pool.max_open() >= 16);
    assert!(pool.max_open() <= MAX_POOL_SIZE);
}

#[test]
fn standalone_roundtrip_zstd() {
    set_k(TEST_K);
    let tmp = NamedTempFile::new().unwrap();
    let sks: Vec<_> = (0..100).map(make_sk).collect();
    {
        let mut w = SKFileWriter::create(tmp.path()).unwrap();
        w.write_batch(&sks).unwrap();
        w.close().unwrap();
    }
    let mut r = SKFileReader::open(tmp.path()).unwrap();
    let got = r.read_batch(200).unwrap();
    assert_eq!(got.len(), 100);
    for (a, b) in sks.iter().zip(got.iter()) {
        assert_eq!(a.to_ascii(), b.to_ascii());
    }
}

#[test]
fn standalone_close_prevents_write() {
    set_k(TEST_K);
    let tmp = NamedTempFile::new().unwrap();
    let mut w = SKFileWriter::create(tmp.path()).unwrap();
    w.close().unwrap();
    assert!(!w.is_open());
    assert!(w.write(&make_sk(0)).is_err());
}

#[test]
fn standalone_is_physically_open() {
    set_k(TEST_K);
    let tmp = NamedTempFile::new().unwrap();
    let mut w = SKFileWriter::create(tmp.path()).unwrap();
    assert!(!w.is_physically_open());
    w.set_flush_threshold(1);
    w.write(&make_sk(0)).unwrap();
    assert!(w.is_physically_open());
    w.close().unwrap();
    assert!(!w.is_physically_open());
}
