/*
  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 Lesser General Public License as published by the Free Software
  Foundation; either version 2.1, 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 Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License along with
  TALER; see the file COPYING.LGPL.  If not, see
  <http://www.gnu.org/licenses/>
*/
/**
 * @file merchant_api_wallet_post_order_refund.c
 * @brief Implementation of the (public) POST /orders/ID/refund request
 * @author Jonathan Buchanan
 */
#include "platform.h"
#include <curl/curl.h>
#include <jansson.h>
#include <microhttpd.h> /* just for HTTP status codes */
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_curl_lib.h>
#include "taler_merchant_service.h"
#include "merchant_api_common.h"
#include "merchant_api_curl_defaults.h"
#include <taler/taler_json_lib.h>
#include <taler/taler_signatures.h>
#include <taler/taler_curl_lib.h>

/**
 * Maximum number of refunds we return.
 */
#define MAX_REFUNDS 1024

/**
 * Handle for a (public) POST /orders/ID/refund operation.
 */
struct TALER_MERCHANT_WalletOrderRefundHandle
{
  /**
   * Complete URL where the backend offers /refund
   */
  char *url;

  /**
   * Minor context that holds body and headers.
   */
  struct TALER_CURL_PostContext post_ctx;

  /**
   * The CURL context to connect to the backend
   */
  struct GNUNET_CURL_Context *ctx;

  /**
   * The callback to pass the backend response to
   */
  TALER_MERCHANT_WalletRefundCallback cb;

  /**
   * Clasure to pass to the callback
   */
  void *cb_cls;

  /**
   * Handle for the request
   */
  struct GNUNET_CURL_Job *job;
};


/**
 * Callback to process (public) POST /orders/ID/refund response
 *
 * @param cls the `struct TALER_MERCHANT_OrderRefundHandle`
 * @param response_code HTTP response code, 0 on error
 * @param response response body, NULL if not JSON
 */
static void
handle_refund_finished (void *cls,
                        long response_code,
                        const void *response)
{
  struct TALER_MERCHANT_WalletOrderRefundHandle *orh = cls;
  const json_t *json = response;
  struct TALER_MERCHANT_WalletRefundResponse wrr = {
    .hr.http_status = (unsigned int) response_code,
    .hr.reply = json
  };

  orh->job = NULL;
  switch (response_code)
  {
  case 0:
    wrr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    break;
  case MHD_HTTP_OK:
    {
      const json_t *refunds;
      unsigned int refund_len;
      struct GNUNET_JSON_Specification spec[] = {
        TALER_JSON_spec_amount_any (
          "refund_amount",
          &wrr.details.ok.refund_amount),
        GNUNET_JSON_spec_array_const (
          "refunds",
          &refunds),
        GNUNET_JSON_spec_fixed_auto (
          "merchant_pub",
          &wrr.details.ok.merchant_pub),
        GNUNET_JSON_spec_end ()
      };

      if (GNUNET_OK !=
          GNUNET_JSON_parse (json,
                             spec,
                             NULL, NULL))
      {
        GNUNET_break_op (0);
        wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
        wrr.hr.http_status = 0;
        break;
      }
      refund_len = json_array_size (refunds);
      if ( (json_array_size (refunds) != (size_t)  refund_len) ||
           (refund_len > MAX_REFUNDS) )
      {
        GNUNET_break (0);
        wrr.hr.ec = TALER_EC_GENERIC_ALLOCATION_FAILURE;
        wrr.hr.http_status = 0;
        break;
      }
      {
        struct TALER_MERCHANT_RefundDetail rds[GNUNET_NZL (refund_len)];

        memset (rds,
                0,
                sizeof (rds));
        for (unsigned int i = 0; i<refund_len; i++)
        {
          struct TALER_MERCHANT_RefundDetail *rd = &rds[i];
          const json_t *jrefund = json_array_get (refunds,
                                                  i);
          const char *refund_status_type;
          uint32_t exchange_status;
          uint32_t eec = 0;
          struct GNUNET_JSON_Specification espec[] = {
            GNUNET_JSON_spec_string ("type",
                                     &refund_status_type),
            GNUNET_JSON_spec_uint32 ("exchange_status",
                                     &exchange_status),
            GNUNET_JSON_spec_uint64 ("rtransaction_id",
                                     &rd->rtransaction_id),
            GNUNET_JSON_spec_fixed_auto ("coin_pub",
                                         &rd->coin_pub),
            TALER_JSON_spec_amount_any ("refund_amount",
                                        &rd->refund_amount),
            GNUNET_JSON_spec_mark_optional (
              GNUNET_JSON_spec_object_const ("exchange_reply",
                                             &rd->hr.reply),
              NULL),
            GNUNET_JSON_spec_mark_optional (
              GNUNET_JSON_spec_uint32 ("exchange_code",
                                       &eec),
              NULL),
            GNUNET_JSON_spec_end ()
          };

          if (GNUNET_OK !=
              GNUNET_JSON_parse (jrefund,
                                 espec,
                                 NULL, NULL))
          {
            GNUNET_break_op (0);
            wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
            wrr.hr.http_status = 0;
            goto finish;
          }

          rd->hr.http_status = exchange_status;
          rd->hr.ec = (enum TALER_ErrorCode) eec;
          switch (exchange_status)
          {
          case MHD_HTTP_OK:
            {
              struct GNUNET_JSON_Specification rspec[] = {
                GNUNET_JSON_spec_fixed_auto ("exchange_sig",
                                             &rd->details.ok.exchange_sig),
                GNUNET_JSON_spec_fixed_auto ("exchange_pub",
                                             &rd->details.ok.exchange_pub),
                GNUNET_JSON_spec_end ()
              };

              if (GNUNET_OK !=
                  GNUNET_JSON_parse (jrefund,
                                     rspec,
                                     NULL,
                                     NULL))
              {
                GNUNET_break_op (0);
                wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
                wrr.hr.http_status = 0;
                goto finish;
              }
              /* check that type field is correct */
              if (0 != strcmp ("success",
                               refund_status_type))
              {
                GNUNET_break_op (0);
                wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
                wrr.hr.http_status = 0;
                goto finish;
              }
            }
            break; /* end MHD_HTTP_OK */
          default:
            if (0 != strcmp ("failure",
                             refund_status_type))
            {
              GNUNET_break_op (0);
              wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
              wrr.hr.http_status = 0;
              goto finish;
            }
          } /* switch on exchange status code */
        } /* for all refunds */

        wrr.details.ok.refunds = rds;
        wrr.details.ok.refunds_length = refund_len;
        orh->cb (orh->cb_cls,
                 &wrr);
        TALER_MERCHANT_wallet_post_order_refund_cancel (orh);
        return;
      } /* end 'rds' scope */
    } /* case MHD_HTTP_OK */
    break;
  case MHD_HTTP_NO_CONTENT:
    break;
  case MHD_HTTP_CONFLICT:
  case MHD_HTTP_NOT_FOUND:
    wrr.hr.ec = TALER_JSON_get_error_code (json);
    wrr.hr.hint = TALER_JSON_get_error_hint (json);
    break;
  default:
    GNUNET_break_op (0); /* unexpected status code */
    TALER_MERCHANT_parse_error_details_ (json,
                                         response_code,
                                         &wrr.hr);
    break;
  }
finish:
  orh->cb (orh->cb_cls,
           &wrr);
  TALER_MERCHANT_wallet_post_order_refund_cancel (orh);
}


struct TALER_MERCHANT_WalletOrderRefundHandle *
TALER_MERCHANT_wallet_post_order_refund (
  struct GNUNET_CURL_Context *ctx,
  const char *backend_url,
  const char *order_id,
  const struct TALER_PrivateContractHashP *h_contract_terms,
  TALER_MERCHANT_WalletRefundCallback cb,
  void *cb_cls)
{
  struct TALER_MERCHANT_WalletOrderRefundHandle *orh;
  json_t *req;
  CURL *eh;

  orh = GNUNET_new (struct TALER_MERCHANT_WalletOrderRefundHandle);
  orh->ctx = ctx;
  orh->cb = cb;
  orh->cb_cls = cb_cls;
  {
    char *path;

    GNUNET_asprintf (&path,
                     "orders/%s/refund",
                     order_id);
    orh->url = TALER_url_join (backend_url,
                               path,
                               NULL);
    GNUNET_free (path);
  }
  if (NULL == orh->url)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Could not construct request URL.\n");
    GNUNET_free (orh);
    return NULL;
  }
  req = GNUNET_JSON_PACK (
    GNUNET_JSON_pack_data_auto ("h_contract",
                                h_contract_terms));
  eh = TALER_MERCHANT_curl_easy_get_ (orh->url);
  if (GNUNET_OK !=
      TALER_curl_easy_post (&orh->post_ctx,
                            eh,
                            req))
  {
    GNUNET_break (0);
    json_decref (req);
    curl_easy_cleanup (eh);
    GNUNET_free (orh->url);
    GNUNET_free (orh);
    return NULL;
  }
  json_decref (req);
  orh->job = GNUNET_CURL_job_add2 (ctx,
                                   eh,
                                   orh->post_ctx.headers,
                                   &handle_refund_finished,
                                   orh);
  if (NULL == orh->job)
  {
    GNUNET_free (orh->url);
    GNUNET_free (orh);
    return NULL;
  }
  return orh;
}


void
TALER_MERCHANT_wallet_post_order_refund_cancel (
  struct TALER_MERCHANT_WalletOrderRefundHandle *orh)
{
  if (NULL != orh->job)
  {
    GNUNET_CURL_job_cancel (orh->job);
    orh->job = NULL;
  }
  TALER_curl_easy_post_finished (&orh->post_ctx);
  GNUNET_free (orh->url);
  GNUNET_free (orh);
}


/* end of merchant_api_wallet_post_order_refund.c */
