#include "mongocrypt.h"

#include "test-mongocrypt.h"
#include "mongocrypt-util-private.h"

static mongocrypt_t *
get_test_mongocrypt (_mongocrypt_tester_t *tester)
{
   mongocrypt_t *crypt = mongocrypt_new ();
   mongocrypt_setopt_log_handler (crypt, _mongocrypt_stdout_log_fn, NULL);
   mongocrypt_binary_t *schema_map = TEST_FILE ("./test/data/schema-map.json");
   ASSERT_OK (
      mongocrypt_setopt_kms_provider_aws (crypt, "example", -1, "example", -1),
      crypt);
   ASSERT_OK (mongocrypt_setopt_schema_map (crypt, schema_map), crypt);
   return crypt;
}

static void
_test_csfle_no_paths (_mongocrypt_tester_t *tester)
{
   /// Test that mongocrypt_init succeeds if we have no search path
   mongocrypt_t *const crypt = get_test_mongocrypt (tester);
   ASSERT_OK (mongocrypt_init (crypt), crypt);
   // No csfle was loaded:
   BSON_ASSERT (mongocrypt_crypt_shared_lib_version_string (crypt, NULL) == NULL);
   BSON_ASSERT (mongocrypt_crypt_shared_lib_version (crypt) == 0);
   mongocrypt_destroy (crypt);
}

static void
_test_csfle_not_found (_mongocrypt_tester_t *tester)
{
   /// Test that mongocrypt_init succeeds even if the csfle library was not
   /// found but a search path was specified
   mongocrypt_t *const crypt = get_test_mongocrypt (tester);
   mongocrypt_setopt_append_crypt_shared_lib_search_path (crypt,
                                                          "/no-such-directory");
   ASSERT_OK (mongocrypt_init (crypt), crypt);
   // No csfle was loaded:
   BSON_ASSERT (mongocrypt_crypt_shared_lib_version_string (crypt, NULL) == NULL);
   BSON_ASSERT (mongocrypt_crypt_shared_lib_version (crypt) == 0);
   mongocrypt_destroy (crypt);
}

static void
_test_csfle_load (_mongocrypt_tester_t *tester)
{
   mongocrypt_t *const crypt = get_test_mongocrypt (tester);
   mongocrypt_setopt_append_crypt_shared_lib_search_path (crypt,
                                                          "no-such-directory");
   mongocrypt_setopt_append_crypt_shared_lib_search_path (crypt, "$ORIGIN");
   mongocrypt_setopt_append_crypt_shared_lib_search_path (
      crypt, "another-no-such-dir");
   ASSERT_OK (mongocrypt_init (crypt), crypt);
   // csfle WAS loaded:
   BSON_ASSERT (mongocrypt_crypt_shared_lib_version_string (crypt, NULL) != NULL);
   BSON_ASSERT (mongocrypt_crypt_shared_lib_version (crypt) != 0);
   mstr_view version =
      mstrv_view_cstr (mongocrypt_crypt_shared_lib_version_string (crypt, NULL));
   if (TEST_MONGOCRYPT_HAVE_REAL_CRYPT_SHARED_LIB) {
      MSTR_ASSERT (true, version, starts_with, mstrv_lit ("mongo_crypt_v1-"));
   } else {
      MSTR_ASSERT (true, version, eq, mstrv_lit ("stubbed-crypt_shared"));
   }
   mongocrypt_destroy (crypt);
}

static void
_test_csfle_load_twice (_mongocrypt_tester_t *tester)
{
   mongocrypt_t *const crypt1 = get_test_mongocrypt (tester);
   mongocrypt_setopt_append_crypt_shared_lib_search_path (crypt1, "$ORIGIN");
   ASSERT_OK (mongocrypt_init (crypt1), crypt1);
   BSON_ASSERT (mongocrypt_crypt_shared_lib_version_string (crypt1, NULL) != NULL);
   BSON_ASSERT (mongocrypt_crypt_shared_lib_version (crypt1) != 0);

   // Make another one:
   mongocrypt_t *const crypt2 = get_test_mongocrypt (tester);
   mongocrypt_setopt_append_crypt_shared_lib_search_path (crypt2, "$ORIGIN");
   ASSERT_OK (mongocrypt_init (crypt2), crypt2);
   // csfle was loaded again:
   BSON_ASSERT (mongocrypt_crypt_shared_lib_version_string (crypt2, NULL) != NULL);
   BSON_ASSERT (mongocrypt_crypt_shared_lib_version (crypt2) != 0);

   mstr_view version =
      mstrv_view_cstr (mongocrypt_crypt_shared_lib_version_string (crypt1, NULL));
   if (TEST_MONGOCRYPT_HAVE_REAL_CRYPT_SHARED_LIB) {
      MSTR_ASSERT (true, version, starts_with, mstrv_lit ("mongo_crypt_v1-"));
   } else {
      MSTR_ASSERT (true, version, eq, mstrv_lit ("stubbed-crypt_shared"));
   }

   version = mstrv_view_cstr (mongocrypt_crypt_shared_lib_version_string (crypt2, NULL));
   if (TEST_MONGOCRYPT_HAVE_REAL_CRYPT_SHARED_LIB) {
      MSTR_ASSERT (true, version, starts_with, mstrv_lit ("mongo_crypt_v1-"));
   } else {
      MSTR_ASSERT (true, version, eq, mstrv_lit ("stubbed-crypt_shared"));
   }

   mongocrypt_destroy (crypt1);
   mongocrypt_destroy (crypt2);
}

static void
_test_csfle_load_twice_fail (_mongocrypt_tester_t *tester)
{
   mongocrypt_t *const crypt1 = get_test_mongocrypt (tester);
   mongocrypt_setopt_append_crypt_shared_lib_search_path (crypt1, "$ORIGIN");
   ASSERT_OK (mongocrypt_init (crypt1), crypt1);
   BSON_ASSERT (mongocrypt_crypt_shared_lib_version_string (crypt1, NULL) != NULL);
   BSON_ASSERT (mongocrypt_crypt_shared_lib_version (crypt1) != 0);

   // Make another one, but finding a different dynamic library:
   mongocrypt_t *const crypt2 = get_test_mongocrypt (tester);
   mongocrypt_setopt_set_crypt_shared_lib_path_override (
      crypt2, "$ORIGIN/stubbed-crypt_shared-2.dll");
   // Loading a second different library is an error:
   ASSERT_FAILS (mongocrypt_init (crypt2),
                 crypt2,
                 "attempted to load a second CSFLE library");

   mstr_view version =
      mstrv_view_cstr (mongocrypt_crypt_shared_lib_version_string (crypt1, NULL));
   if (TEST_MONGOCRYPT_HAVE_REAL_CRYPT_SHARED_LIB) {
      MSTR_ASSERT (true, version, starts_with, mstrv_lit ("mongo_crypt_v1-"));
   } else {
      MSTR_ASSERT (true, version, eq, mstrv_lit ("stubbed-crypt_shared"));
   }

   mongocrypt_destroy (crypt1);
   mongocrypt_destroy (crypt2);
}

static void
_test_csfle_path_override_okay (_mongocrypt_tester_t *tester)
{
   mongocrypt_t *const crypt = get_test_mongocrypt (tester);
   // Set to the absolute path to the DLL we use for testing:
   mongocrypt_setopt_set_crypt_shared_lib_path_override (
      crypt, "$ORIGIN/mongo_crypt_v1" MCR_DLL_SUFFIX);
   ASSERT_OK (mongocrypt_init (crypt), crypt);
   // csfle WAS loaded:
   BSON_ASSERT (mongocrypt_crypt_shared_lib_version_string (crypt, NULL) != NULL);
   BSON_ASSERT (mongocrypt_crypt_shared_lib_version (crypt) != 0);
   mstr_view version =
      mstrv_view_cstr (mongocrypt_crypt_shared_lib_version_string (crypt, NULL));
   if (TEST_MONGOCRYPT_HAVE_REAL_CRYPT_SHARED_LIB) {
      MSTR_ASSERT (true, version, starts_with, mstrv_lit ("mongo_crypt_v1-"));
   } else {
      MSTR_ASSERT (true, version, eq, mstrv_lit ("stubbed-crypt_shared"));
   }
   mongocrypt_destroy (crypt);
}

static void
_test_csfle_path_override_fail (_mongocrypt_tester_t *tester)
{
   mongocrypt_t *const crypt = get_test_mongocrypt (tester);
   // Set to the absolute path to a file that does not exist
   mongocrypt_setopt_set_crypt_shared_lib_path_override (
      crypt, "/no-such-file-or-directory/mongo_crypt_v1" MCR_DLL_SUFFIX);
   // This *would* succeed, but we don't use the search paths if an absolute
   // override was specified:
   mongocrypt_setopt_append_crypt_shared_lib_search_path (crypt, "$ORIGIN");
   ASSERT_FAILS (mongocrypt_init (crypt),
                 crypt,
                 "but we failed to open a dynamic library at that location");
   BSON_ASSERT (mongocrypt_crypt_shared_lib_version_string (crypt, NULL) == NULL);
   BSON_ASSERT (mongocrypt_crypt_shared_lib_version (crypt) == 0);
   mongocrypt_destroy (crypt);
}

static void
_test_cur_exe_path (_mongocrypt_tester_t *tester)
{
   current_module_result self = current_module_path ();
   BSON_ASSERT (self.error == 0);
   BSON_ASSERT (self.path.len != 0);
   mstr_free (self.path);
}

static void
_test_csfle_not_loaded_with_bypassqueryanalysis (_mongocrypt_tester_t *tester)
{
   mongocrypt_t *const crypt = get_test_mongocrypt (tester);
   mongocrypt_setopt_append_crypt_shared_lib_search_path (crypt, "$ORIGIN");
   mongocrypt_setopt_bypass_query_analysis (crypt);
   ASSERT_OK (mongocrypt_init (crypt), crypt);
   BSON_ASSERT (mongocrypt_crypt_shared_lib_version_string (crypt, NULL) == NULL);
   BSON_ASSERT (mongocrypt_crypt_shared_lib_version (crypt) == 0);

   mongocrypt_destroy (crypt);
}

void
_mongocrypt_tester_install_csfle_lib (_mongocrypt_tester_t *tester)
{
   INSTALL_TEST (_test_csfle_no_paths);
   INSTALL_TEST (_test_csfle_not_found);
   INSTALL_TEST (_test_csfle_load);
   INSTALL_TEST (_test_csfle_load_twice);
   INSTALL_TEST (_test_csfle_load_twice_fail);
   INSTALL_TEST (_test_csfle_path_override_okay);
   INSTALL_TEST (_test_csfle_path_override_fail);
   INSTALL_TEST (_test_cur_exe_path);
   INSTALL_TEST (_test_csfle_not_loaded_with_bypassqueryanalysis);
}
