#![cfg(all(target_arch = "wasm32", target_os = "unknown"))]

//! IndexedDB tests.

use futures::future::TryFutureExt as _;
use keyvaluedb_shared_tests as st;
use keyvaluedb_web::{Database, KeyValueDB as _};
use wasm_bindgen_test::*;

wasm_bindgen_test_configure!(run_in_browser);

async fn new_db(col: u32, name: &str, memory_cached: bool) -> Database {
    maybe_delete_db(name).await;
    Database::open(name, col, memory_cached)
        .unwrap_or_else(|err| panic!("{}", err))
        .await
}

async fn open_db(col: u32, name: &str, memory_cached: bool) -> Database {
    Database::open(name, col, memory_cached)
        .unwrap_or_else(|err| panic!("{}", err))
        .await
}

async fn maybe_delete_db(name: &str) {
    let _ = Database::delete(name).await;
}

// Tests Without Cache

#[wasm_bindgen_test]
async fn get_fails_with_non_existing_column_no_cache() {
    let db = new_db(1, "get_fails_with_non_existing_column_no_cache", false).await;
    st::test_get_fails_with_non_existing_column(db)
        .await
        .unwrap()
}

#[wasm_bindgen_test]
async fn put_and_get_no_cache() {
    let db = new_db(1, "put_and_get_no_cache", false).await;
    st::test_put_and_get(db).await.unwrap()
}

#[wasm_bindgen_test]
async fn num_keys_no_cache() {
    let db = new_db(1, "num_keys_no_cache", false).await;
    st::test_num_keys(db).await.unwrap()
}

#[wasm_bindgen_test]
async fn delete_and_get_no_cache() {
    let db = new_db(1, "delete_and_get_no_cache", false).await;
    st::test_delete_and_get(db).await.unwrap();
}

#[wasm_bindgen_test]
async fn delete_and_get_single_no_cache() {
    let db = new_db(1, "delete_and_get_single_no_cache", false).await;
    st::test_delete_and_get_single(db).await.unwrap();
}

#[wasm_bindgen_test]
async fn delete_prefix_no_cache() {
    let db = new_db(
        st::DELETE_PREFIX_NUM_COLUMNS,
        "delete_prefix_no_cache",
        false,
    )
    .await;
    st::test_delete_prefix(db).await.unwrap();
}

#[wasm_bindgen_test]
async fn iter_no_cache() {
    let db = new_db(1, "iter_no_cache", false).await;
    st::test_iter(db).await.unwrap();
}

#[wasm_bindgen_test]
async fn iter_keys_no_cache() {
    let db = new_db(1, "iter_keys_no_cache", false).await;
    st::test_iter_keys(db).await.unwrap();
}

#[wasm_bindgen_test]
async fn iter_with_prefix_no_cache() {
    let db = new_db(1, "iter_with_prefix_no_cache", false).await;
    st::test_iter_with_prefix(db).await.unwrap();
}

#[wasm_bindgen_test]
async fn complex_no_cache() {
    let db = new_db(1, "complex_no_cache", false).await;
    st::test_complex(db).await.unwrap();
}

#[wasm_bindgen_test]
async fn cleanup_no_cache() {
    let db = new_db(1, "cleanup_no_cache", false).await;
    st::test_cleanup(db).await.unwrap();
}

#[wasm_bindgen_test]
async fn reopen_the_database_with_more_columns_no_cache() {
    let _ = console_log::init_with_level(log::Level::Trace);

    let db = new_db(1, "reopen_the_database_with_more_columns_no_cache", false).await;

    // Write a value into the database
    let mut batch = db.transaction();
    batch.put(0, b"hello", b"world");
    db.write(batch).await.unwrap();

    assert_eq!(db.get(0, b"hello").await.unwrap().unwrap(), b"world");

    // Check the database version
    assert_eq!(db.version(), 1);

    // Close the database
    drop(db);

    // Reopen it again with 3 columns
    let db = open_db(3, "reopen_the_database_with_more_columns_no_cache", false).await;

    // The value should still be present
    assert_eq!(db.get(0, b"hello").await.unwrap().unwrap(), b"world");
    assert!(db.get(0, b"trash").await.unwrap().is_none());

    // The version should be bumped
    assert_eq!(db.version(), 2);
}

// Tests With Cache

#[wasm_bindgen_test]
async fn get_fails_with_non_existing_column_with_cache() {
    let db = new_db(1, "get_fails_with_non_existing_column_with_cache", true).await;
    st::test_get_fails_with_non_existing_column(db)
        .await
        .unwrap()
}

#[wasm_bindgen_test]
async fn put_and_get_with_cache() {
    let db = new_db(1, "put_and_get_with_cache", true).await;
    st::test_put_and_get(db).await.unwrap()
}

#[wasm_bindgen_test]
async fn num_keys_with_cache() {
    let db = new_db(1, "num_keys_with_cache", true).await;
    st::test_num_keys(db).await.unwrap()
}

#[wasm_bindgen_test]
async fn delete_and_get_with_cache() {
    let db = new_db(1, "delete_and_get_with_cache", true).await;
    st::test_delete_and_get(db).await.unwrap();
}

#[wasm_bindgen_test]
async fn delete_and_get_single_with_cache() {
    let db = new_db(1, "delete_and_get_single_with_cache", true).await;
    st::test_delete_and_get_single(db).await.unwrap();
}

#[wasm_bindgen_test]
async fn delete_prefix_with_cache() {
    let db = new_db(
        st::DELETE_PREFIX_NUM_COLUMNS,
        "delete_prefix_with_cache",
        true,
    )
    .await;
    st::test_delete_prefix(db).await.unwrap();
}

#[wasm_bindgen_test]
async fn iter_with_cache() {
    let db = new_db(1, "iter_with_cache", true).await;
    st::test_iter(db).await.unwrap();
}

#[wasm_bindgen_test]
async fn iter_keys_with_cache() {
    let db = new_db(1, "iter_keys_with_cache", true).await;
    st::test_iter_keys(db).await.unwrap();
}

#[wasm_bindgen_test]
async fn iter_with_prefix_with_cache() {
    let db = new_db(1, "iter_with_prefix_with_cache", true).await;
    st::test_iter_with_prefix(db).await.unwrap();
}

#[wasm_bindgen_test]
async fn complex_with_cache() {
    let db = new_db(1, "complex_with_cache", true).await;
    st::test_complex(db).await.unwrap();
}

#[wasm_bindgen_test]
async fn cleanup_with_cache() {
    let db = new_db(1, "cleanup_with_cache", true).await;
    st::test_cleanup(db).await.unwrap();
}

#[wasm_bindgen_test]
async fn list_enumerates_open_databases() {
    let _ = console_log::init_with_level(log::Level::Trace);

    let names = [
        "kvweb_list_test_alpha",
        "kvweb_list_test_beta",
        "kvweb_list_test_gamma",
    ];
    // Use a unique prefix so we don't collide with other concurrent tests
    let prefix = "kvweb_list_test_";

    // Clean up any leftover dbs from a previous failed run
    for n in &names {
        maybe_delete_db(n).await;
    }
    // Sanity: nothing matching the prefix should remain
    let before = Database::list(Some(prefix)).await.unwrap();
    assert!(
        before.is_empty(),
        "expected no leftover dbs with prefix {prefix:?}, got {:?}",
        before
    );

    // Open three databases
    let mut handles = Vec::with_capacity(names.len());
    for n in &names {
        handles.push(open_db(1, n, false).await);
    }

    // Filtered enumeration returns exactly the three we created
    let mut listed = Database::list(Some(prefix)).await.unwrap();
    listed.sort_by(|a, b| a.0.cmp(&b.0));
    assert_eq!(listed.len(), names.len(), "listed = {:?}", listed);
    for (i, n) in names.iter().enumerate() {
        assert_eq!(&listed[i].0, n);
        assert!(listed[i].1 >= 1);
    }

    // Unfiltered enumeration includes our three (may include other dbs too)
    let unfiltered = Database::list(None).await.unwrap();
    for n in &names {
        assert!(
            unfiltered.iter().any(|(name, _)| name == n),
            "unfiltered list missing {n}: got {unfiltered:?}"
        );
    }

    // Drop handles, then delete; afterward the filtered list is empty again
    drop(handles);
    for n in &names {
        Database::delete(n).await.unwrap();
    }
    let after = Database::list(Some(prefix)).await.unwrap();
    assert!(
        after.is_empty(),
        "expected list to be empty after delete, got {:?}",
        after
    );
}

#[wasm_bindgen_test]
async fn reopen_the_database_with_more_columns_with_cache() {
    let _ = console_log::init_with_level(log::Level::Trace);

    let db = new_db(1, "reopen_the_database_with_more_columns_with_cache", true).await;

    // Write a value into the database
    let mut batch = db.transaction();
    batch.put(0, b"hello", b"world");
    db.write(batch).await.unwrap();

    assert_eq!(db.get(0, b"hello").await.unwrap().unwrap(), b"world");

    // Check the database version
    assert_eq!(db.version(), 1);

    // Close the database
    drop(db);

    // Reopen it again with 3 columns
    let db = open_db(3, "reopen_the_database_with_more_columns_with_cache", true).await;

    // The value should still be present
    assert_eq!(db.get(0, b"hello").await.unwrap().unwrap(), b"world");
    assert!(db.get(0, b"trash").await.unwrap().is_none());

    // The version should be bumped
    assert_eq!(db.version(), 2);
}
