#[cfg(feature = "derive")]
mod derive_feature_test;
#[cfg(feature = "derive")]
mod efficient_agdb;
#[cfg(feature = "openapi")]
mod openapi_feature_test;
#[cfg(feature = "derive")]
mod quickstart;
#[cfg(feature = "serde")]
mod serde_feature_test;
mod test_db;

use agdb::Db;
use agdb::DbAny;
use agdb::DbElement;
use agdb::DbFile;
use agdb::DbId;
use agdb::DbMemory;
use agdb::MemoryStorage;
use agdb::QueryBuilder;
use agdb::QueryId;
use agdb::StorageData;
use std::sync::Arc;
use std::sync::RwLock;
use std::time::Instant;
use test_db::TestDb;
use test_db::test_file::TestFile;

#[allow(unused_imports)]
#[test]
fn public_types() {
    use agdb::AgdbSerialize;
    use agdb::Comparison;
    use agdb::CountComparison;
    use agdb::Db;
    use agdb::DbElement;
    use agdb::DbError;
    use agdb::DbF64;
    use agdb::DbFile;
    use agdb::DbFileTransaction;
    use agdb::DbFileTransactionMut;
    use agdb::DbId;
    use agdb::DbImpl;
    use agdb::DbKeyOrder;
    use agdb::DbKeyValue;
    use agdb::DbMemory;
    use agdb::DbMemoryTransaction;
    use agdb::DbMemoryTransactionMut;
    use agdb::DbTransaction;
    use agdb::DbTransactionMut;
    use agdb::DbType;
    use agdb::DbValue;
    use agdb::FileStorage;
    use agdb::FileStorageMemoryMapped;
    use agdb::InsertAliasesQuery;
    use agdb::InsertEdgesQuery;
    use agdb::InsertIndexQuery;
    use agdb::InsertNodesQuery;
    use agdb::InsertValuesQuery;
    use agdb::MemoryStorage;
    use agdb::Query;
    use agdb::QueryBuilder;
    use agdb::QueryCondition;
    use agdb::QueryConditionData;
    use agdb::QueryConditionLogic;
    use agdb::QueryConditionModifier;
    use agdb::QueryId;
    use agdb::QueryIds;
    use agdb::QueryMut;
    use agdb::QueryResult;
    use agdb::QueryValues;
    use agdb::RemoveAliasesQuery;
    use agdb::RemoveQuery;
    use agdb::RemoveValuesQuery;
    use agdb::SearchQuery;
    use agdb::SearchQueryAlgorithm;
    use agdb::SelectAliasesQuery;
    use agdb::SelectAllAliasesQuery;
    use agdb::SelectEdgeCountQuery;
    use agdb::SelectIndexesQuery;
    use agdb::SelectKeyCountQuery;
    use agdb::SelectKeysQuery;
    use agdb::SelectNodeCountQuery;
    use agdb::SelectValuesQuery;
    use agdb::StableHash;
    use agdb::StorageData;
    use agdb::StorageSlice;
    use agdb::Transaction;
    use agdb::TransactionMut;
}

#[test]
fn data_persistence() {
    let test_file = TestFile::new();
    let values = vec![
        ("key", "String that is much longer than 15 characters").into(),
        (-10_i64, 2000000000000_i64).into(),
        (10_u64, 1.1_f64).into(),
        (vec!["Some", "List"], vec![1_u64, 2_u64]).into(),
        (
            vec![-1_i64, -2_i64, -3_i64],
            vec![-3.3_f64, -3.4_f64, -720.984_f64],
        )
            .into(),
        (vec![3_u8; 5], vec![15_u8; 20]).into(),
    ];

    {
        let mut db = Db::new(test_file.file_name()).unwrap();
        db.exec_mut(
            QueryBuilder::insert()
                .nodes()
                .aliases(["alias", "alias2"])
                .values_uniform(values.clone())
                .query(),
        )
        .unwrap();
        db.exec_mut(QueryBuilder::insert().edges().from(1).to(2).query())
            .unwrap();
        let result = db
            .exec(
                QueryBuilder::select()
                    .ids([QueryId::from("alias"), "alias2".into(), (-3).into()])
                    .query(),
            )
            .unwrap();

        assert_eq!(
            result.elements,
            &[
                DbElement {
                    id: DbId(1),
                    from: DbId(-3),
                    to: DbId::default(),
                    values: values.clone(),
                },
                DbElement {
                    id: DbId(2),
                    from: DbId::default(),
                    to: DbId(-3),
                    values: values.clone(),
                },
                DbElement {
                    id: DbId(-3),
                    from: DbId(1),
                    to: DbId(2),
                    values: vec![],
                }
            ]
        );
    }

    let db = Db::new(test_file.file_name()).unwrap();
    let result = db
        .exec(
            QueryBuilder::select()
                .ids([QueryId::from("alias"), "alias2".into(), (-3).into()])
                .query(),
        )
        .unwrap();

    assert_eq!(
        result.elements,
        &[
            DbElement {
                id: DbId(1),
                from: DbId(-3),
                to: DbId::default(),
                values: values.clone(),
            },
            DbElement {
                id: DbId(2),
                from: DbId::default(),
                to: DbId(-3),
                values,
            },
            DbElement {
                id: DbId(-3),
                from: DbId(1),
                to: DbId(2),
                values: vec![],
            }
        ]
    );
}

#[test]
fn data_remove_persistence() {
    let test_file = TestFile::new();

    {
        let mut db = Db::new(test_file.file_name()).unwrap();
        db.exec_mut(
            QueryBuilder::insert()
                .nodes()
                .aliases(["alias", "alias2"])
                .values_uniform([("key", 100).into()])
                .query(),
        )
        .unwrap();
        db.exec_mut(QueryBuilder::insert().edges().from(1).to(2).query())
            .unwrap();
        let result = db
            .exec(
                QueryBuilder::select()
                    .ids([QueryId::from("alias"), "alias2".into(), (-3).into()])
                    .query(),
            )
            .unwrap();

        assert_eq!(
            result.elements,
            &[
                DbElement {
                    id: DbId(1),
                    from: DbId(-3),
                    to: DbId::default(),
                    values: vec![("key", 100).into()],
                },
                DbElement {
                    id: DbId(2),
                    from: DbId::default(),
                    to: DbId(-3),
                    values: vec![("key", 100).into()],
                },
                DbElement {
                    id: DbId(-3),
                    from: DbId(1),
                    to: DbId(2),
                    values: vec![],
                }
            ]
        );

        db.exec_mut(QueryBuilder::remove().ids(-3).query()).unwrap();
        db.exec_mut(QueryBuilder::remove().values("key").ids(1).query())
            .unwrap();
    }

    let db = Db::new(test_file.file_name()).unwrap();
    let result = db
        .exec(QueryBuilder::select().ids(["alias", "alias2"]).query())
        .unwrap();

    assert_eq!(
        result.elements,
        &[
            DbElement {
                id: DbId(1),
                from: DbId::default(),
                to: DbId::default(),
                values: vec![],
            },
            DbElement {
                id: DbId(2),
                from: DbId::default(),
                to: DbId::default(),
                values: vec![("key", 100).into()],
            }
        ]
    );

    let error = db.exec(QueryBuilder::select().ids(-3).query()).unwrap_err();
    assert_eq!(error.description, "Id '-3' not found");
}

#[test]
fn invalid_db_file() {
    let test_file = TestFile::new();
    let bytes = [0_u64.to_le_bytes(), 0_u64.to_le_bytes()].concat();
    std::fs::write(test_file.file_name(), bytes).unwrap();
    let error = Db::new(test_file.file_name()).unwrap_err();

    assert_eq!(
        error.description,
        format!("Failed to create database: {}", test_file.file_name())
    );
    assert_eq!(
        error.cause.unwrap().description,
        "Invalid version record size (0 < 8)"
    );
}

#[test]
fn optimize_on_drop() {
    let test_file = TestFile::new();
    let db_file_size;

    {
        let mut db = Db::new(test_file.file_name()).unwrap();
        let result = db
            .exec_mut(
                QueryBuilder::insert()
                    .nodes()
                    .count(1000)
                    .values_uniform([("key", "value").into()])
                    .query(),
            )
            .unwrap();
        db.exec_mut(QueryBuilder::remove().ids(result).query())
            .unwrap();
        db_file_size = std::fs::File::open(test_file.file_name())
            .unwrap()
            .metadata()
            .unwrap()
            .len();
    }

    let optimized_file_size = std::fs::File::open(test_file.file_name())
        .unwrap()
        .metadata()
        .unwrap()
        .len();

    assert!(optimized_file_size < db_file_size);
}

#[test]
fn share_between_threads() {
    let test_file = TestFile::new();
    let db = Arc::new(RwLock::new(Db::new(test_file.file_name()).unwrap()));
    db.write()
        .unwrap()
        .exec_mut(QueryBuilder::insert().nodes().count(1).query())
        .unwrap();
    let db2 = db.clone();

    let t1 = std::thread::spawn(move || {
        db.read()
            .unwrap()
            .exec(QueryBuilder::search().from(1).query())
            .unwrap()
    });
    let t2 = std::thread::spawn(move || {
        db2.read()
            .unwrap()
            .exec(QueryBuilder::search().from(1).query())
            .unwrap()
    });

    t1.join().unwrap();
    t2.join().unwrap();
}

#[test]
fn hot_backup() {
    let test_file = TestFile::new();
    let signal = Arc::new(RwLock::new(true));
    let db = Arc::new(RwLock::new(Db::new(test_file.file_name()).unwrap()));
    db.write()
        .unwrap()
        .exec_mut(QueryBuilder::insert().nodes().count(1).query())
        .unwrap();

    let db2 = db.clone();
    let signal2 = signal.clone();
    let t1 = std::thread::spawn(move || {
        while *signal2.read().unwrap() {
            db2.write()
                .unwrap()
                .exec_mut(QueryBuilder::insert().nodes().count(1).query())
                .unwrap();
            std::thread::sleep(std::time::Duration::from_millis(10));
        }
    });

    let test_file2 = TestFile::new();
    db.write().unwrap().backup(test_file2.file_name()).unwrap();
    *signal.write().unwrap() = false;
    let db = Db::new(test_file2.file_name()).unwrap();
    assert_eq!(
        db.exec(QueryBuilder::select().ids(1).query())
            .unwrap()
            .result,
        1
    );
    t1.join().unwrap();
}

#[test]
fn memory_backup() {
    let test_file = TestFile::new();

    {
        let mut db = DbMemory::new("memdb").unwrap();
        db.exec_mut(QueryBuilder::insert().nodes().count(1).query())
            .unwrap();
        db.backup(test_file.file_name()).unwrap();
        assert!(std::fs::exists(test_file.file_name()).unwrap());
        db.exec_mut(QueryBuilder::insert().nodes().count(1).query())
            .unwrap();
    }

    let db = DbMemory::new(test_file.file_name()).unwrap();
    assert_eq!(
        db.exec(QueryBuilder::select().node_count().query())
            .unwrap()
            .result,
        1
    );
}

#[test]
fn filename() {
    let test_file = TestFile::new();
    let db = Db::new(test_file.file_name()).unwrap();
    assert_eq!(db.filename(), test_file.file_name());
}

#[test]
fn optimize_storage() {
    let mut db = TestDb::new();

    db.exec_mut(
        QueryBuilder::insert()
            .nodes()
            .count(100)
            .values_uniform([("key", 123).into()])
            .query(),
        100,
    );

    let size = db.db.size();
    db.db.optimize_storage().unwrap();
    let optimized_size = db.db.size();

    assert!(optimized_size < size);
}

#[test]
fn rename_memory() {
    let mut db = DbMemory::new("memdb").unwrap();
    db.rename("mydb").unwrap();
    assert_eq!(db.filename(), "mydb");
}

#[test]
fn rename_mapped() {
    let test_file = TestFile::new();
    let test_file2 = TestFile::new();
    let mut db = Db::new(test_file.file_name()).unwrap();
    db.rename(test_file2.file_name()).unwrap();
    assert_eq!(db.filename(), test_file2.file_name());
}

#[test]
fn rename_file() {
    let test_file = TestFile::new();
    let test_file2 = TestFile::new();
    {
        let mut db = DbFile::new(test_file.file_name()).unwrap();
        db.exec_mut(QueryBuilder::insert().nodes().count(1).query())
            .unwrap();
        db.rename(test_file2.file_name()).unwrap();
        assert_eq!(
            db.exec(QueryBuilder::select().ids(1).query())
                .unwrap()
                .result,
            1
        );
    }
    let db = DbFile::new(test_file2.file_name()).unwrap();
    assert_eq!(
        db.exec(QueryBuilder::select().ids(1).query())
            .unwrap()
            .result,
        1
    );
}

#[test]
fn copy_memory() {
    let mut db = DbMemory::new("memdb").unwrap();
    db.exec_mut(QueryBuilder::insert().nodes().aliases("root").query())
        .unwrap();
    let other = db.copy("mydb").unwrap();
    assert_eq!(other.filename(), "mydb");
    assert_eq!(
        other
            .exec(QueryBuilder::select().ids("root").query())
            .unwrap()
            .result,
        1
    );
}

#[test]
fn copy_mapped() {
    let test_file = TestFile::new();
    let test_file2 = TestFile::new();
    let mut db = Db::new(test_file.file_name()).unwrap();
    db.exec_mut(QueryBuilder::insert().nodes().aliases("root").query())
        .unwrap();
    let other = db.copy(test_file2.file_name()).unwrap();
    assert_eq!(other.filename(), test_file2.file_name());
    assert_eq!(
        other
            .exec(QueryBuilder::select().ids("root").query())
            .unwrap()
            .result,
        1
    );
}

#[test]
fn copy_file() {
    let test_file = TestFile::new();
    let test_file2 = TestFile::new();
    let mut db = DbFile::new(test_file.file_name()).unwrap();
    db.exec_mut(QueryBuilder::insert().nodes().aliases("root").query())
        .unwrap();
    let other = db.copy(test_file2.file_name()).unwrap();
    assert_eq!(other.filename(), test_file2.file_name());
    assert_eq!(
        other
            .exec(QueryBuilder::select().ids("root").query())
            .unwrap()
            .result,
        1
    );
}

#[test]
fn queries_as_reference() {
    let test_file = TestFile::new();
    let mut db = DbFile::new(test_file.file_name()).unwrap();

    let query = QueryBuilder::insert()
        .nodes()
        .aliases(["root", "users"])
        .query();
    db.exec_mut(&query).unwrap();

    let query = QueryBuilder::insert().aliases("root").ids("root").query();
    db.exec_mut(&query).unwrap();

    let query = QueryBuilder::insert()
        .edges()
        .from("root")
        .to("users")
        .query();
    db.exec_mut(&query).unwrap();

    let query = QueryBuilder::insert().index("username").query();
    db.exec_mut(&query).unwrap();

    let query = QueryBuilder::insert()
        .values([[("username", "admin").into()]])
        .ids("users")
        .query();
    db.exec_mut(&query).unwrap();

    let query = QueryBuilder::remove().aliases("new_root").query();
    db.exec_mut(&query).unwrap();

    let query = QueryBuilder::remove().index("username").query();
    db.exec_mut(&query).unwrap();

    let query = QueryBuilder::remove()
        .values("username")
        .ids("users")
        .query();
    db.exec_mut(&query).unwrap();

    let query = QueryBuilder::remove().ids("users").query();
    db.exec_mut(&query).unwrap();

    let query = QueryBuilder::search().from("root").query();
    db.exec(&query).unwrap();

    let query = QueryBuilder::select().aliases().query();
    db.exec(&query).unwrap();

    let query = QueryBuilder::select().aliases().ids(1).query();
    db.exec(&query).unwrap();

    let query = QueryBuilder::select().edge_count().ids("root").query();
    db.exec(&query).unwrap();

    let query = QueryBuilder::select().edge_count_from().ids("root").query();
    db.exec(&query).unwrap();

    let query = QueryBuilder::select().edge_count_to().ids("root").query();
    db.exec(&query).unwrap();

    let query = QueryBuilder::select().indexes().query();
    db.exec(&query).unwrap();

    let query = QueryBuilder::select().key_count().ids("root").query();
    db.exec(&query).unwrap();

    let query = QueryBuilder::select().keys().ids("root").query();
    db.exec(&query).unwrap();

    let query = QueryBuilder::select().node_count().query();
    db.exec(&query).unwrap();

    let query = QueryBuilder::select().ids("root").query();
    db.exec(&query).unwrap();
}

#[test]
fn large_queries() {
    const COUNT: i64 = 10000;
    let mut db = DbMemory::new("test").unwrap();
    db.exec_mut(QueryBuilder::insert().nodes().count(COUNT as u64).query())
        .unwrap();

    let mut now = Instant::now();
    let mut times = vec![];

    for (from, to) in (1..COUNT).step_by(2).zip((2..COUNT).step_by(2)) {
        let r = db
            .exec_mut(QueryBuilder::insert().edges().from(from).to(to).query())
            .unwrap();
        let vals: Vec<agdb::DbKeyValue> = (1..10)
            .map(|j| (format!("key{}", from + j), format!("value{}", from + j)).into())
            .collect();
        db.exec_mut(
            QueryBuilder::insert()
                .values([vals])
                .ids(r.elements[0].id)
                .query(),
        )
        .unwrap();

        if to % 1000 == 0 {
            times.push(now.elapsed());
            now = Instant::now();
        }
    }

    times.push(now.elapsed());

    let min = times.iter().min().unwrap();
    let max = times.iter().max().unwrap();
    let avg = times.iter().map(|t| t.as_millis() as u64).sum::<u64>() / times.len() as u64;

    println!("Min: {}ms", min.as_millis());
    println!("Max: {}ms", max.as_millis());
    println!("Avg: {avg}ms");
    println!("{times:?}");
}

#[test]
fn convert_db_before_0_11_0() {
    let test_file = TestFile::new();
    std::fs::copy("tests/test_db_prior_0_11_0.agdb", test_file.file_name()).unwrap();
    let db = Db::new(test_file.file_name()).unwrap();
    let result = db
        .exec(QueryBuilder::select().search().elements().query())
        .unwrap();
    assert_eq!(result.elements.len(), 3);
    assert_eq!(
        result.elements[0].values,
        vec![("key1", "value1").into(), ("key2", 123).into()]
    );
    assert_eq!(result.elements[1].values, vec![(1, 2).into()]);
    assert_eq!(result.elements[2].values, vec![("tag", "label").into()]);
}

#[test]
fn db_any() {
    let test_file1 = TestFile::new();
    let test_file2 = TestFile::new();

    let dbs = [
        DbAny::new_file(test_file1.file_name()).unwrap(),
        DbAny::new_mapped(test_file2.file_name()).unwrap(),
        DbAny::new_memory("memdb").unwrap(),
    ];

    let names = dbs.iter().map(|db| db.filename()).collect::<Vec<&str>>();

    assert_eq!(
        names,
        &[test_file1.file_name(), test_file2.file_name(), "memdb"]
    );
}

#[test]
fn db_any_struct() {
    struct MyDb {
        db: DbAny,
    }

    let test_file = TestFile::new();
    let db = DbAny::new(test_file.file_name()).unwrap();
    let mut my_db = MyDb { db };

    assert_eq!(my_db.db.filename(), test_file.file_name());

    my_db = MyDb {
        db: DbAny::new_memory("memdb").unwrap(),
    };

    assert_eq!(my_db.db.filename(), "memdb");
}

#[test]
fn with_data() {
    let mut db = DbMemory::with_data(MemoryStorage::new("test").unwrap()).unwrap();
    db.exec_mut(QueryBuilder::insert().nodes().count(1).query())
        .unwrap();
    let count = db
        .exec(QueryBuilder::select().node_count().query())
        .unwrap()
        .result;

    assert_eq!(count, 1);
}

#[test]
fn using_ref_insert_element() {
    struct S {}

    impl agdb::DbType for S {
        type ValueType = Self;

        fn db_id(&self) -> Option<QueryId> {
            None
        }

        fn db_keys() -> Vec<agdb::DbValue> {
            vec![]
        }

        fn from_db_element(_element: &agdb::DbElement) -> Result<Self::ValueType, agdb::DbError> {
            Ok(S {})
        }

        fn to_db_values(&self) -> Vec<agdb::DbKeyValue> {
            vec![]
        }
    }

    let mut db = DbMemory::new("test").unwrap();
    let s = S {};
    db.exec_mut(QueryBuilder::insert().element(&s).query())
        .unwrap();
    db.exec_mut(QueryBuilder::insert().element(s).query())
        .unwrap();
}

#[test]
fn shrink_to_fit() {
    let test_file = TestFile::new();
    let mut db = DbMemory::new(test_file.file_name()).unwrap();
    db.exec_mut(QueryBuilder::insert().index("key0").query())
        .unwrap();
    db.exec_mut(QueryBuilder::insert().nodes().aliases("root").query())
        .unwrap();

    for i in 0..1000 {
        let mut values = vec![];
        for j in 0..(i % 10) {
            values.push((format!("key{}", j), format!("value{}", j)).into());
        }
        let node = db
            .exec_mut(QueryBuilder::insert().nodes().values([values]).query())
            .unwrap();
        db.exec_mut(QueryBuilder::insert().edges().from("root").to(node).query())
            .unwrap();
    }

    let size = db.size();
    db.optimize_storage().unwrap();
    let optimized_size = db.size();
    db.shrink_to_fit().unwrap();
    let shrunk_size = db.size();

    assert!(
        optimized_size < size,
        "{optimized_size} (optimized) < {size} (original)"
    );
    assert!(
        shrunk_size < optimized_size,
        "{shrunk_size} (shrunk) < {optimized_size} (optimized)"
    );
}
