/*
  This file is part of TALER
  Copyright (C) 2020-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_wallet_post_orders_refund.c
 * @brief command to test refunds.
 * @author Marcello Stanisci
 * @author Christian Grothoff
 */
#include "platform.h"
#include <taler/taler_exchange_service.h>
#include <taler/taler_testing_lib.h>
#include "taler_merchant_service.h"
#include "taler_merchant_testing_lib.h"


/**
 * State for an "obtain refunds" CMD.
 */
struct WalletRefundState
{
  /**
   * Operation handle for a (public) POST /orders/$ID/refund request.
   */
  struct TALER_MERCHANT_WalletOrderRefundHandle *orh;

  /**
   * Base URL of the merchant serving the request.
   */
  const char *merchant_url;

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

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

  /**
   * Label of the command that created the order we want to obtain refunds for.
   */
  const char *proposal_reference;

  /**
   * A list of refunds associated with this order.
   */
  const char **refunds;

  /**
   * The length of @e refunds.
   */
  unsigned int refunds_length;
};


/**
 * Process POST /refund (increase) response; just checking
 * if the HTTP response code is the one expected.
 *
 * @param cls closure
 * @param wrr response
 */
static void
refund_cb (
  void *cls,
  const struct TALER_MERCHANT_WalletRefundResponse *wrr)
{
  struct WalletRefundState *wrs = cls;

  wrs->orh = NULL;
  if (wrs->http_code != wrr->hr.http_status)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Expected status %u, got %u(%d) for refund increase\n",
                wrs->http_code,
                wrr->hr.http_status,
                (int) wrr->hr.ec);
    TALER_TESTING_FAIL (wrs->is);
  }
  switch (wrr->hr.http_status)
  {
  case MHD_HTTP_OK:
    {
      struct TALER_Amount refunded_total;
      if (wrr->details.ok.refunds_length > 0)
        GNUNET_assert (GNUNET_OK ==
                       TALER_amount_set_zero (
                         wrr->details.ok.refunds[0].refund_amount.currency,
                         &refunded_total));
      for (unsigned int i = 0; i < wrr->details.ok.refunds_length; ++i)
      {
        const struct TALER_MERCHANT_RefundDetail *refund
          = &wrr->details.ok.refunds[wrr->details.ok.refunds_length - 1 - i];
        const struct TALER_TESTING_Command *refund_cmd;
        const struct TALER_Amount *expected_amount;

        refund_cmd = TALER_TESTING_interpreter_lookup_command (
          wrs->is,
          wrs->refunds[i]);

        if (GNUNET_OK !=
            TALER_TESTING_get_trait_amount (refund_cmd,
                                            &expected_amount))
        {
          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                      "Could not fetch refund amount\n");
          TALER_TESTING_interpreter_fail (wrs->is);
          return;
        }
        /* The most recent refunds are returned first */
        GNUNET_assert (0 <= TALER_amount_add (&refunded_total,
                                              &refunded_total,
                                              &refund->refund_amount));
        if ( (GNUNET_OK !=
              TALER_amount_cmp_currency (expected_amount,
                                         &refunded_total)) ||
             (0 != TALER_amount_cmp (expected_amount,
                                     &refunded_total)) )
        {
          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                      "Refund amounts do not match\n");
          TALER_TESTING_interpreter_fail (wrs->is);
          return;
        }
      }
    }
    break;
  default:
    break;
  }
  TALER_TESTING_interpreter_next (wrs->is);
}


/**
 * Run the "refund increase" CMD.
 *
 * @param cls closure.
 * @param cmd command currently being run.
 * @param is the interpreter state.
 */
static void
obtain_refunds_run (void *cls,
                    const struct TALER_TESTING_Command *cmd,
                    struct TALER_TESTING_Interpreter *is)
{
  struct WalletRefundState *wrs = cls;
  const struct TALER_TESTING_Command *proposal_cmd =
    TALER_TESTING_interpreter_lookup_command (is,
                                              wrs->proposal_reference);
  const struct TALER_PrivateContractHashP *h_contract_terms;
  const char *order_id;

  if (NULL == proposal_cmd)
    TALER_TESTING_FAIL (is);
  if (GNUNET_OK !=
      TALER_TESTING_get_trait_h_contract_terms (proposal_cmd,
                                                &h_contract_terms))
    TALER_TESTING_FAIL (is);

  {
    const json_t *contract_terms;
    const char *error_name;
    unsigned int error_line;

    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_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);
      }
    }
  }

  wrs->is = is;
  wrs->orh = TALER_MERCHANT_wallet_post_order_refund (
    TALER_TESTING_interpreter_get_context (is),
    wrs->merchant_url,
    order_id,
    h_contract_terms,
    &refund_cb,
    wrs);
  if (NULL == wrs->orh)
    TALER_TESTING_FAIL (is);
}


/**
 * Free the state of a "refund increase" CMD, and
 * possibly cancel a pending "refund increase" operation.
 *
 * @param cls closure
 * @param cmd command currently being freed.
 */
static void
obtain_refunds_cleanup (void *cls,
                        const struct TALER_TESTING_Command *cmd)
{
  struct WalletRefundState *wrs = cls;

  if (NULL != wrs->orh)
  {
    TALER_LOG_WARNING ("Refund operation did not complete\n");
    TALER_MERCHANT_wallet_post_order_refund_cancel (wrs->orh);
  }
  GNUNET_array_grow (wrs->refunds,
                     wrs->refunds_length,
                     0);
  GNUNET_free (wrs);
}


struct TALER_TESTING_Command
TALER_TESTING_cmd_wallet_order_refund (const char *label,
                                       const char *merchant_url,
                                       const char *order_ref,
                                       unsigned int http_code,
                                       ...)
{
  struct WalletRefundState *wrs;

  wrs = GNUNET_new (struct WalletRefundState);
  wrs->merchant_url = merchant_url;
  wrs->proposal_reference = order_ref;
  wrs->http_code = http_code;
  wrs->refunds_length = 0;
  {
    const char *clabel;
    va_list ap;

    va_start (ap, http_code);
    while (NULL != (clabel = va_arg (ap, const char *)))
    {
      GNUNET_array_append (wrs->refunds,
                           wrs->refunds_length,
                           clabel);
    }
    va_end (ap);
  }
  {
    struct TALER_TESTING_Command cmd = {
      .cls = wrs,
      .label = label,
      .run = &obtain_refunds_run,
      .cleanup = &obtain_refunds_cleanup
    };

    return cmd;
  }
}
