/*
  This file is part of TALER
  Copyright (C) 2024 Taler Systems SA

  TALER is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
  Foundation; either version 3, or (at your option) any later version.

  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

  You should have received a copy of the GNU General Public License along with
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file donaudb/test_donaudb.c
 * @brief test cases for DB interaction functions
 * @author Johannes Casaburi
 */
#include "donau_config.h"
#include <taler/taler_json_lib.h>
#include "donaudb_lib.h"
#include "donau_util.h"
#include "donaudb_plugin.h"

/**
 * Global result from the testcase.
 */
static int result;

/**
 * Report line of error if @a cond is true, and jump to label "drop".
 */
#define FAILIF(cond)                              \
  do {                                          \
    if (! (cond)) { break;}                      \
    GNUNET_break (0);                           \
    goto drop;                                  \
  } while (0)


/**
 * Initializes @a ptr with random data.
 */
#define RND_BLK(ptr)                                                    \
  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, sizeof (* \
                                                                       ptr))

/**
 * Initializes @a ptr with zeros.
 */
#define ZR_BLK(ptr) \
  memset (ptr, 0, sizeof (*ptr))

/**
 * How big do we make the RSA keys?
 */
#define RSA_KEY_SIZE 1024

/**
 * Currency we use.  Must match test-donau-db-*.conf.
 */
#define CURRENCY "EUR"

/**
 * Database plugin under test.
 */
static struct DONAUDB_Plugin *plugin;

/**
 * Return charities information.
 *
 * @param cls closure
 */
static enum GNUNET_GenericReturnValue
charities_cb (
  void *cls,
  uint64_t charity_id,
  const struct DONAU_CharityPublicKeyP *charity_pub,
  const char *charity_name,
  const struct TALER_Amount *max_per_year,
  uint32_t current_year,
  const struct TALER_Amount *receipts_to_date)
{
  (void) cls;
  (void) charity_id;
  (void) charity_name;
  (void) max_per_year;
  (void) current_year;
  (void) receipts_to_date;
  return GNUNET_OK;
}


/**
 * Function called with information about the donau's donation_unit keys.
 *
 * @param cls NULL
 * @param donation_unit_pub public key of the donation_unit
 * @param h_donation_unit_pub hash of @a donation_unit_pub
 * @param validity_year of the donation unit
 * @param value of the donation unit
 */
static enum GNUNET_GenericReturnValue
donation_unit_info_cb (
  void *cls,
  const struct DONAU_DonationUnitHashP *h_donation_unit_pub,
  const struct DONAU_DonationUnitPublicKey *donation_unit_pub,
  uint64_t validity_year,
  struct TALER_Amount *value)
{
  (void) cls;
  (void) h_donation_unit_pub;
  (void) donation_unit_pub;
  (void) validity_year;
  (void) value;
  return GNUNET_OK;
}


/**
 * Function called with information about the donau's online signing keys.
 *
 * @param cls NULL
 * @param donau_pub the public key
 * @param meta meta data information about the denomination type (expirations)
 */
static void
iterate_active_signing_keys_cb (
  void *cls,
  const struct DONAU_DonauPublicKeyP *donau_pub,
  struct DONAUDB_SignkeyMetaData *meta)
{
  (void) cls;
  (void) donau_pub;
  (void) meta;
}


/**
 * Main function that will be run by the scheduler.
 *
 * @param cls closure with config
 */
static void
run (void *cls)
{
  struct GNUNET_CONFIGURATION_Handle *cfg = cls;
  struct GNUNET_TIME_Timestamp now;

  // Charity information
  json_t *charities;
  struct DONAU_CharityPublicKeyP charity_pub;
  struct DONAUDB_CharityMetaData charity_meta;
  const char *charity_name;
  const char *charity_url;
  struct TALER_Amount max_per_year;
  struct TALER_Amount receipts_to_date;
  uint64_t charity_id;

  // Donation unit information
  struct DONAU_DonationUnitHashP h_donation_unit_pub;
  uint64_t validity_year;
  struct TALER_Amount du_value;

  // Signing key information
  struct DONAU_DonauPublicKeyP donau_pub;
  struct DONAUDB_SignkeyMetaData sk_meta;

  // Issued receipts information
  size_t num_b_sigs = 1;
  struct DONAU_BlindedDonationUnitSignature du_sigs[num_b_sigs];
  struct DONAU_DonationReceiptHashP h_receipt;
  struct TALER_Amount amount_receipts;
  bool smaller_than_max_per_year;
  struct TALER_DenominationPrivateKey denom_priv;
  struct TALER_DenominationPublicKey denom_pub;
  struct DONAU_DonationUnitPublicKey du_pub;
  struct GNUNET_CRYPTO_BlindedMessage *rp;
  struct GNUNET_CRYPTO_RsaBlindedMessage *rsa;

  if (NULL ==
      (plugin = DONAUDB_plugin_load (cfg)))
  {
    fprintf (stderr,
             "Failed to load DB plugin\n");
    result = 77;
    return;
  }
  (void) plugin->drop_tables (plugin->cls);
  if (GNUNET_OK !=
      plugin->create_tables (plugin->cls))
  {
    fprintf (stderr,
             "Failed to create DB tables\n");
    result = 77;
    goto cleanup;
  }
  plugin->preflight (plugin->cls);
  FAILIF (GNUNET_OK !=
          plugin->start (plugin->cls,
                         "test-1"));

  fprintf (stderr,
           "Running DB tests\n");

  /* test DB is empty */
  charity_id = 1;
  FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
          plugin->lookup_charity (plugin->cls,
                                  charity_id,
                                  &charity_meta));

  /* test insert charity */
  charity_name = "charity_name";
  charity_url = "charity_url";
  charities = json_array ();
  RND_BLK (&charity_pub);
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount (CURRENCY ":1.000010",
                                         &max_per_year));
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount (CURRENCY ":0.000010",
                                         &receipts_to_date));

  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
          plugin->insert_charity (plugin->cls,
                                  &charity_pub,
                                  charity_name,
                                  charity_url,
                                  &max_per_year,
                                  &charity_id));

  /* test get charities */
  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
          plugin->get_charities (plugin->cls,
                                 &charities_cb,
                                 charities));

  {
    /* Update the charity and verify the new key and metadata persist. */
    const char *updated_charity_name = "charity_name_updated";
    const char *updated_charity_url = "charity_url_updated";
    struct TALER_Amount updated_max;
    struct DONAU_CharityPrivateKeyP updated_charity_priv;
    struct DONAU_CharityPublicKeyP updated_charity_pub;

    GNUNET_CRYPTO_eddsa_key_create (&updated_charity_priv.eddsa_priv);
    GNUNET_CRYPTO_eddsa_key_get_public (&updated_charity_priv.eddsa_priv,
                                        &updated_charity_pub.eddsa_pub);

    GNUNET_assert (GNUNET_OK ==
                   TALER_string_to_amount (CURRENCY ":2.000000",
                                           &updated_max));
    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
            plugin->update_charity (plugin->cls,
                                    charity_id,
                                    &updated_charity_pub,
                                    updated_charity_name,
                                    updated_charity_url,
                                    &updated_max));

    ZR_BLK (&charity_meta);
    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
            plugin->lookup_charity (plugin->cls,
                                    charity_id,
                                    &charity_meta));
    GNUNET_assert (0 == GNUNET_memcmp (&charity_meta.charity_pub,
                                       &updated_charity_pub));
    GNUNET_assert (0 == strcmp (charity_meta.charity_name,
                                updated_charity_name));
    GNUNET_assert (0 == strcmp (charity_meta.charity_url,
                                updated_charity_url));
    GNUNET_assert (0 == TALER_amount_cmp (&charity_meta.max_per_year,
                                          &updated_max));
    GNUNET_free (charity_meta.charity_name);
    GNUNET_free (charity_meta.charity_url);

    charity_name = updated_charity_name;
    charity_url = updated_charity_url;
    max_per_year = updated_max;
    charity_pub = updated_charity_pub;
  }

  /* test delete charity */
  FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
          plugin->do_charity_delete (plugin->cls,
                                     charity_id));

  /* test insert donation unit */
  RND_BLK (&h_donation_unit_pub);
  GNUNET_assert (GNUNET_OK ==
                 TALER_denom_priv_create (&denom_priv,
                                          &denom_pub,
                                          GNUNET_CRYPTO_BSA_RSA,
                                          RSA_KEY_SIZE));
  du_pub.bsign_pub_key = denom_pub.bsign_pub_key;
  validity_year = 2024;
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount (CURRENCY ":1.000010",
                                         &du_value));

  /* test iterate donation units */
  FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
          plugin->iterate_donation_units (plugin->cls,
                                          &donation_unit_info_cb,
                                          NULL));

  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
          plugin->insert_donation_unit (plugin->cls,
                                        &h_donation_unit_pub,
                                        &du_pub,
                                        validity_year,
                                        &du_value));

  /* test iterate donation units */
  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
          plugin->iterate_donation_units (plugin->cls,
                                          &donation_unit_info_cb,
                                          NULL));

  TALER_denom_pub_free (&denom_pub);

  /* test insert signing key */
  RND_BLK (&donau_pub);
  now = GNUNET_TIME_timestamp_get ();
  sk_meta.expire_legal = now;
  sk_meta.expire_sign = now;
  sk_meta.valid_from = now;

  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
          plugin->insert_signing_key (plugin->cls,
                                      &donau_pub,
                                      &sk_meta));

  /* test iterate signing key */
  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
          plugin->iterate_active_signing_keys (plugin->cls,
                                               &iterate_active_signing_keys_cb,
                                               NULL));

  /* test insert issued receipt */
  rp = GNUNET_new (struct GNUNET_CRYPTO_BlindedMessage);
  rp->cipher = GNUNET_CRYPTO_BSA_RSA;
  rp->rc = 1;
  rsa = &rp->details.rsa_blinded_message;
  rsa->blinded_msg_size = 1 + (size_t) GNUNET_CRYPTO_random_u64 (
    GNUNET_CRYPTO_QUALITY_WEAK,
    (RSA_KEY_SIZE / 8) - 1);
  rsa->blinded_msg = GNUNET_malloc (rsa->blinded_msg_size);
  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
                              rsa->blinded_msg,
                              rsa->blinded_msg_size);
  smaller_than_max_per_year = false;
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount (CURRENCY ":1.000010",
                                         &amount_receipts));

  du_sigs[0].blinded_sig =
    GNUNET_CRYPTO_blind_sign (denom_priv.bsign_priv_key,
                              "rw",
                              rp);

  GNUNET_assert (NULL != du_sigs[0].blinded_sig);
  TALER_denom_priv_free (&denom_priv);

  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
          plugin->insert_issued_receipt (plugin->cls,
                                         num_b_sigs,
                                         du_sigs,
                                         charity_id,
                                         &h_receipt,
                                         &amount_receipts,
                                         &smaller_than_max_per_year));

  // FIXME
  // FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
  //         plugin->lookup_issued_receipts (plugin->cls,
  //                                         &h_receipt,
  //                                         &ir_meta));

  /* test insert submitted receipts */
  // RND_BLK (&h_donor_tax_id);
  // RND_BLK (&donation_receipts[0].h_donation_unit_pub);
  // RND_BLK (&donation_receipts[0].nonce);
  // RND_BLK (&donation_receipts[0].donation_unit_sig);
  // FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
  //         plugin->insert_submitted_receipts (plugin->cls,
  //                                            &h_donor_tax_id,
  //                                            num_dr,
  //                                            donation_receipts,
  //                                            current_year));

  plugin->preflight (plugin->cls);

  result = 0;

drop:
  if (0 != result)
    plugin->rollback (plugin->cls);
  GNUNET_break (GNUNET_OK ==
                plugin->drop_tables (plugin->cls));
cleanup:
  DONAUDB_plugin_unload (plugin);
  plugin = NULL;
}


int
main (int argc,
      char *const argv[])
{
  const char *plugin_name;
  char *config_filename;
  char *testname;
  struct GNUNET_CONFIGURATION_Handle *cfg;

  (void) argc;
  result = -1;
  if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
  {
    GNUNET_break (0);
    return -1;
  }
  GNUNET_log_setup (argv[0],
                    "WARNING",
                    NULL);
  plugin_name++;
  (void) GNUNET_asprintf (&testname,
                          "test-donau-db-%s",
                          plugin_name);
  (void) GNUNET_asprintf (&config_filename,
                          "%s.conf",
                          testname);
  cfg = GNUNET_CONFIGURATION_create (DONAU_project_data ());
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_parse (cfg,
                                  config_filename))
  {
    GNUNET_break (0);
    GNUNET_free (config_filename);
    GNUNET_free (testname);
    return 2;
  }
  GNUNET_SCHEDULER_run (&run,
                        cfg);
  GNUNET_CONFIGURATION_destroy (cfg);
  GNUNET_free (config_filename);
  GNUNET_free (testname);
  return result;
}


/* end of test_donaudb.c */
