mod testprotocol;
mod testsupport;

use crate::testprotocol::acme::user::v1::{AccessToken, Bucket};
use crate::testsupport::get_config_store;
use pbbson::Model;
use pbjson_types::Timestamp;
use saas_rs_sdk::storage::Error;
use saas_rs_sdk::storage::config_store::FindOptions;

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn can_create_access_token_and_auto_assign_id() {
    let config_store = get_config_store!();
    let access_token = AccessToken { ..Default::default() };
    let access_token: AccessToken = config_store
        .create(Bucket::AccessTokens, Model::try_from(&access_token).unwrap())
        .await
        .expect("Expected to be able to create access token")
        .try_into()
        .unwrap();
    assert!(!access_token.id.is_empty());
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn can_create_access_token_with_caller_assigned_xid() {
    let config_store = get_config_store!();
    let id = xid::new().to_string();
    let access_token = AccessToken {
        id: id.clone(),
        ..Default::default()
    };
    let access_token: AccessToken = config_store
        .create(Bucket::AccessTokens, Model::try_from(&access_token).unwrap())
        .await
        .expect("Expected to be able to create access token")
        .try_into()
        .unwrap();
    assert_eq!(access_token.id, id);
}

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

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn cannot_find_non_existent_refresh_token() {
    let config_store = get_config_store!();
    let id = xid::new().to_string();
    let res = config_store.find(Bucket::RefreshTokens, &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() {
    let config_store = get_config_store!();

    // Create access token
    let access_token = AccessToken {
        id: xid::new().to_string(),
        expires_at: Some(Timestamp {
            seconds: 1749046039,
            nanos: 0,
        }),
        expires_in: Some(pbjson_types::Duration {
            seconds: 123,
            nanos: 234,
        }),
        ..Default::default()
    };
    let access_token: AccessToken = config_store
        .create(Bucket::AccessTokens, Model::try_from(&access_token).unwrap())
        .await
        .expect("Expected to be able to create access token")
        .try_into()
        .unwrap();

    let refresh_token = xid::new().to_string();
    let mut refresh_token_model = Model::default();
    refresh_token_model.insert("id", &refresh_token);
    config_store
        .create(Bucket::RefreshTokens, refresh_token_model)
        .await
        .expect("Expected to be able to create refresh token");

    // Find One access token
    let mut access_token_reloaded: AccessToken = config_store
        .find(Bucket::AccessTokens, &access_token.id)
        .await
        .expect("Expected to be able to find newly created access token")
        .try_into()
        .unwrap();
    assert_eq!(access_token, access_token_reloaded);

    // Find One refresh token
    let maybe = config_store.find(Bucket::RefreshTokens, &refresh_token).await;
    assert!(maybe.is_ok(), "Expected to be able to find newly created refresh token");

    // Find Many
    let access_tokens: Vec<_> = config_store
        .find_many(Bucket::AccessTokens, FindOptions::new())
        .await
        .expect("Expected to be able to find newly created access token in find many operation")
        .into_iter()
        .map(|model| model.try_into().unwrap())
        .collect();
    assert!(access_tokens.contains(&access_token));

    // Update
    access_token_reloaded.description = Some("abc".to_string());
    config_store
        .update(Bucket::AccessTokens, Model::try_from(&access_token_reloaded).unwrap())
        .await
        .expect("Expected to be able to update access token");
    let access_token_re_reloaded: AccessToken = config_store
        .find(Bucket::AccessTokens, &access_token.id)
        .await
        .expect("Expected to be able to find updated access token")
        .try_into()
        .unwrap();
    assert_eq!(access_token_reloaded, access_token_re_reloaded);

    // Delete access token
    config_store
        .delete(Bucket::AccessTokens, &access_token.id, None)
        .await
        .unwrap();

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