/*
  This file is part of TALER
  Copyright (C) 2014-2023 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 testing_api_cmd_abort_order.c
 * @brief command to test the abort feature.
 * @author Marcello Stanisci
 */
#include "platform.h"
#include <taler/taler_exchange_service.h>
#include <taler/taler_testing_lib.h>
#include <taler/taler_signatures.h>
#include "taler_merchant_service.h"
#include "taler_merchant_testing_lib.h"

/**
 * State for a " abort" CMD.
 */
struct AbortState
{

  /**
   * Reference to the "pay" command to abort.
   */
  const char *pay_reference;

  /**
   * Merchant URL.
   */
  const char *merchant_url;

  /**
   * Handle to a "abort" operation.
   */
  struct TALER_MERCHANT_OrderAbortHandle *oah;

  /**
   * Interpreter state.
   */
  struct TALER_TESTING_Interpreter *is;

  /**
   * The actual abort/refund data.
   */
  struct TALER_MERCHANT_AbortedCoin *acs;

  /**
   * Expected HTTP response code.
   */
  unsigned int http_status;

  /**
   * How many refund permissions this CMD got
   * the right for.  Roughly, there is one refund
   * permission for one coin.
   */
  unsigned int acs_length;

};


/**
 * Parse the @a coins specification and grow the @a ac
 * array with the coins found, updating @a nac.
 *
 * @param[in,out] ac pointer to array of coins found
 * @param[in,out] nac length of array at @a pc
 * @param[in] coins string specifying coins to add to @a pc,
 *            clobbered in the process
 * @param is interpreter state
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
build_coins (struct TALER_MERCHANT_AbortCoin **ac,
             unsigned int *nac,
             char *coins,
             struct TALER_TESTING_Interpreter *is)
{
  for (char *token = strtok (coins, ";");
       NULL != token;
       token = strtok (NULL, ";"))
  {
    char *ctok;
    unsigned int ci;
    struct TALER_MERCHANT_AbortCoin *icoin;

    /* Token syntax is "LABEL[/NUMBER]" */
    ctok = strchr (token, '/');
    ci = 0;
    if (NULL != ctok)
    {
      *ctok = '\0';
      ctok++;
      if (1 != sscanf (ctok,
                       "%u",
                       &ci))
      {
        GNUNET_break (0);
        return GNUNET_SYSERR;
      }
    }
    {
      const struct TALER_TESTING_Command *coin_cmd;

      coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
                                                           token);
      if (NULL == coin_cmd)
      {
        GNUNET_break (0);
        return GNUNET_SYSERR;
      }
      GNUNET_array_grow (*ac,
                         *nac,
                         (*nac) + 1);
      icoin = &((*ac)[(*nac) - 1]);

      {
        const struct TALER_CoinSpendPrivateKeyP *coin_priv;

        GNUNET_assert (GNUNET_OK ==
                       TALER_TESTING_get_trait_coin_priv (coin_cmd,
                                                          ci,
                                                          &coin_priv));
        GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
                                            &icoin->coin_pub.eddsa_pub);
      }
      GNUNET_assert (GNUNET_OK ==
                     TALER_TESTING_get_trait_exchange_url (coin_cmd,
                                                           &icoin->exchange_url));
      {
        const struct TALER_Amount *denom_value;

        GNUNET_assert (GNUNET_OK ==
                       TALER_TESTING_get_trait_amount (coin_cmd,
                                                       &denom_value));
        icoin->amount_with_fee = *denom_value;
      }

    }
  }
  return GNUNET_OK;
}


/**
 * Callback for a "pay abort" operation.  Mainly, check HTTP
 * response code was as expected and stores refund permissions
 * in the state.
 *
 * @param cls closure.
 * @param ar response
 */
static void
abort_cb (void *cls,
          const struct TALER_MERCHANT_AbortResponse *ar)
{
  struct AbortState *as = cls;

  as->oah = NULL;
  if (as->http_status != ar->hr.http_status)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unexpected response code %u (%d) to command `%s' (expected %u)\n",
                ar->hr.http_status,
                (int) ar->hr.ec,
                TALER_TESTING_interpreter_get_current_label (as->is),
                as->http_status);
    TALER_TESTING_FAIL (as->is);
  }
  if ( (MHD_HTTP_OK == ar->hr.http_status) &&
       (TALER_EC_NONE == ar->hr.ec) )
  {
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Received %u refunds\n",
                ar->details.ok.num_aborts);
    as->acs_length = ar->details.ok.num_aborts;
    as->acs = GNUNET_new_array (as->acs_length,
                                struct TALER_MERCHANT_AbortedCoin);
    GNUNET_memcpy (as->acs,
                   ar->details.ok.aborts,
                   as->acs_length
                   * sizeof (struct TALER_MERCHANT_AbortedCoin));
  }
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Successful pay-abort (HTTP status: %u)\n",
              ar->hr.http_status);
  TALER_TESTING_interpreter_next (as->is);
}


/**
 * Run an "abort" CMD.
 *
 * @param cls closure
 * @param cmd command being run.
 * @param is interpreter state
 */
static void
abort_run (void *cls,
           const struct TALER_TESTING_Command *cmd,
           struct TALER_TESTING_Interpreter *is)
{
  struct AbortState *as = cls;
  const struct TALER_TESTING_Command *pay_cmd;
  const char *proposal_reference;
  const char *coin_reference;
  const struct TALER_TESTING_Command *proposal_cmd;
  const char *order_id;
  const struct TALER_PrivateContractHashP *h_proposal;
  struct TALER_MerchantPublicKeyP merchant_pub;
  struct TALER_Amount total_amount;
  const char *error_name;
  unsigned int error_line;
  struct TALER_MERCHANT_AbortCoin *abort_coins;
  unsigned int nabort_coins;
  char *cr;

  as->is = is;
  pay_cmd = TALER_TESTING_interpreter_lookup_command (is,
                                                      as->pay_reference);
  if (NULL == pay_cmd)
    TALER_TESTING_FAIL (is);
  if (GNUNET_OK !=
      TALER_TESTING_get_trait_proposal_reference (pay_cmd,
                                                  &proposal_reference))
    TALER_TESTING_FAIL (is);
  if (GNUNET_OK !=
      TALER_TESTING_get_trait_coin_reference (pay_cmd,
                                              0,
                                              &coin_reference))
    TALER_TESTING_FAIL (is);
  proposal_cmd = TALER_TESTING_interpreter_lookup_command (is,
                                                           proposal_reference);

  if (NULL == proposal_cmd)
    TALER_TESTING_FAIL (is);

  {
    const json_t *contract_terms;

    if (GNUNET_OK !=
        TALER_TESTING_get_trait_contract_terms (proposal_cmd,
                                                &contract_terms))
      TALER_TESTING_FAIL (is);
    {
      /* Get information that needs to be put verbatim in the
       * deposit permission */
      struct GNUNET_JSON_Specification spec[] = {
        GNUNET_JSON_spec_string ("order_id",
                                 &order_id),
        GNUNET_JSON_spec_fixed_auto ("merchant_pub",
                                     &merchant_pub),
        TALER_JSON_spec_amount_any ("amount",
                                    &total_amount),
        GNUNET_JSON_spec_end ()
      };

      if (GNUNET_OK !=
          GNUNET_JSON_parse (contract_terms,
                             spec,
                             &error_name,
                             &error_line))
      {
        char *js;

        js = json_dumps (contract_terms,
                         JSON_INDENT (1));
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Parser failed on %s:%u for input `%s'\n",
                    error_name,
                    error_line,
                    js);
        free (js);
        TALER_TESTING_FAIL (is);
      }
    }
  }

  cr = GNUNET_strdup (coin_reference);
  abort_coins = NULL;
  nabort_coins = 0;
  if (GNUNET_OK !=
      build_coins (&abort_coins,
                   &nabort_coins,
                   cr,
                   is))
  {
    GNUNET_array_grow (abort_coins,
                       nabort_coins,
                       0);
    GNUNET_free (cr);
    TALER_TESTING_FAIL (is);
  }
  GNUNET_free (cr);

  if (GNUNET_OK !=
      TALER_TESTING_get_trait_h_contract_terms (proposal_cmd,
                                                &h_proposal))
    TALER_TESTING_FAIL (is);
  as->oah = TALER_MERCHANT_order_abort (TALER_TESTING_interpreter_get_context (
                                          is),
                                        as->merchant_url,
                                        order_id,
                                        &merchant_pub,
                                        h_proposal,
                                        nabort_coins,
                                        abort_coins,
                                        &abort_cb,
                                        as);
  GNUNET_array_grow (abort_coins,
                     nabort_coins,
                     0);
  if (NULL == as->oah)
    TALER_TESTING_FAIL (is);
}


/**
 * Free a "pay abort" CMD, and cancel it if need be.
 *
 * @param cls closure.
 * @param cmd command currently being freed.
 */
static void
abort_cleanup (void *cls,
               const struct TALER_TESTING_Command *cmd)
{
  struct AbortState *as = cls;

  if (NULL != as->oah)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Command `%s' did not complete.\n",
                TALER_TESTING_interpreter_get_current_label (
                  as->is));
    TALER_MERCHANT_order_abort_cancel (as->oah);
  }
  GNUNET_array_grow (as->acs,
                     as->acs_length,
                     0);
  GNUNET_free (as);
}


/**
 * Offer internal data useful to other commands.
 *
 * @param cls closure
 * @param[out] ret result (could be anything)
 * @param trait name of the trait
 * @param index index number of the object to extract.
 * @return #GNUNET_OK on success
 */
static int
abort_traits (void *cls,
              const void **ret,
              const char *trait,
              unsigned int index)
{
  struct AbortState *as = cls;
  struct TALER_TESTING_Trait traits[] = {
    TALER_TESTING_trait_end ()
  };

  (void) as;
  return TALER_TESTING_get_trait (traits,
                                  ret,
                                  trait,
                                  index);
}


struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_order_abort (const char *label,
                                        const char *merchant_url,
                                        const char *pay_reference,
                                        unsigned int http_status)
{
  struct AbortState *as;

  as = GNUNET_new (struct AbortState);
  as->http_status = http_status;
  as->pay_reference = pay_reference;
  as->merchant_url = merchant_url;
  {
    struct TALER_TESTING_Command cmd = {
      .cls = as,
      .label = label,
      .run = &abort_run,
      .cleanup = &abort_cleanup,
      .traits = &abort_traits
    };

    return cmd;
  }
}


/* end of testing_api_cmd_abort_order.c */
