mod testprotocol;
mod testsupport;

use crate::testsupport::get_object_store;
use hex_literal::hex;
use pbbson::Model;
use pbbson::bson::spec::BinarySubtype;
use pbbson::bson::{Binary, Bson};
use saas_rs_sdk::storage::Error;
use std::path::PathBuf;

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn can_create_object() {
    const FILENAME: &str = "build.rs";
    let object_store = get_object_store!();
    let mut metadata = Model::default();
    metadata.insert("filename", FILENAME);
    let path = PathBuf::from(FILENAME);
    let model = object_store
        .create(metadata, &path)
        .await
        .expect("Expected to be able to create object");
    assert!(!model.id().unwrap().is_empty());
    assert!(model.contains_field("createdAt"));
    assert_eq!(model.get("length"), Some(&Bson::Int64(1106)));
    assert_eq!(
        model.get("md5"),
        Some(&Bson::Binary(Binary {
            subtype: BinarySubtype::Md5,
            bytes: hex!("f70160080a18018c12162b770583d571").to_vec()
        }))
    );
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn cannot_create_object_without_filename_in_metadata() {
    const FILENAME: &str = "README.md";
    let object_store = get_object_store!();
    let metadata = Model::default();
    let path = PathBuf::from(FILENAME);
    let res = object_store.create(metadata, &path).await;
    assert!(res.is_err());
    assert!(matches!(res.err().unwrap(), Error::InvalidArgument { .. }));
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn cannot_find_non_existent_object() {
    let object_store = get_object_store!();
    let id = xid::new().to_string();
    let res = object_store.find(&id).await;
    assert!(res.is_err());
    assert!(matches!(res.err().unwrap(), Error::NotFound { .. }));
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn can_perform_crud_operations() {
    // Create object
    const FILENAME: &str = "CHANGELOG.md";
    let object_store = get_object_store!();
    let mut metadata = Model::default();
    metadata.insert("filename", FILENAME);
    let path = PathBuf::from(FILENAME);
    let model = object_store
        .create(metadata, &path)
        .await
        .expect("Expected to be able to create object");
    let model_id = model.id().unwrap();

    // Find One (the metadata)
    let model_reloaded = object_store
        .find(&model_id)
        .await
        .expect("Expected to be able to find newly created object's metadata");
    assert_eq!(model_id, model_reloaded.id().unwrap());
    assert_eq!(model.get("filename").unwrap(), model_reloaded.get("filename").unwrap());
    assert_eq!(model.get("length").unwrap(), model_reloaded.get("length").unwrap());

    // Find Many (the metadata)
    let metadatas: Vec<_> = object_store
        .find_many(Model::default(), None)
        .await
        .expect("Expected to be able to find all object metadatas");
    let found = metadatas.iter().any(|other| other.id().unwrap() == model.id().unwrap());
    assert!(found);

    // Download
    let file = object_store
        .download(&model_id)
        .await
        .expect("Expected to be able to download newly created object");
    let expected = std::fs::read_to_string(FILENAME).unwrap();
    let actual = std::fs::read_to_string(file.path()).unwrap();
    assert_eq!(expected, actual);

    // Delete
    object_store
        .delete(&model_id, None)
        .await
        .expect("Expected to be able to delete newly created object");

    // Find should return a record that is marked as soft-deleted
    let model = object_store.find(&model.id().unwrap()).await.unwrap();
    assert!(model.get("deletedAt").is_some());
}
