/*
  This file is part of TALER
  Copyright (C) 2020-2026 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 src/lib/merchant_api_post-orders-ORDER_ID-refund.c
 * @brief Implementation of the POST /orders/$ORDER_ID/refund request
 * @author Christian Grothoff
 */
#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/post-orders-ORDER_ID-refund.h>
#include "merchant_api_curl_defaults.h"
#include "merchant_api_common.h"
#include <taler/taler_json_lib.h>
#include <taler/taler_curl_lib.h>
#include <taler/taler_signatures.h>


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


/**
 * Handle for a POST /orders/$ORDER_ID/refund operation.
 */
struct TALER_MERCHANT_PostOrdersRefundHandle
{
  /**
   * Base URL of the merchant backend.
   */
  char *base_url;

  /**
   * The full URL for this request.
   */
  char *url;

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

  /**
   * Function to call with the result.
   */
  TALER_MERCHANT_PostOrdersRefundCallback cb;

  /**
   * Closure for @a cb.
   */
  TALER_MERCHANT_POST_ORDERS_REFUND_RESULT_CLOSURE *cb_cls;

  /**
   * Reference to the execution context.
   */
  struct GNUNET_CURL_Context *ctx;

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

  /**
   * Order identifier.
   */
  char *order_id;

  /**
   * Hash of the contract terms.
   */
  struct TALER_PrivateContractHashP h_contract_terms;
};


/**
 * Function called when we're done processing the
 * HTTP POST /orders/$ORDER_ID/refund request.
 *
 * @param cls the `struct TALER_MERCHANT_PostOrdersRefundHandle`
 * @param response_code HTTP response code, 0 on error
 * @param response response body, NULL if not in JSON
 */
static void
handle_refund_finished (void *cls,
                        long response_code,
                        const void *response)
{
  struct TALER_MERCHANT_PostOrdersRefundHandle *porh = cls;
  const json_t *json = response;
  struct TALER_MERCHANT_PostOrdersRefundResponse orr = {
    .hr.http_status = (unsigned int) response_code,
    .hr.reply = json
  };

  porh->job = NULL;
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "POST /orders/$ID/refund completed with response code %u\n",
              (unsigned int) response_code);
  switch (response_code)
  {
  case 0:
    orr.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[] = {
        GNUNET_JSON_spec_array_const (
          "refunds",
          &refunds),
        TALER_JSON_spec_amount_any (
          "refund_amount",
          &orr.details.ok.refund_amount),
        GNUNET_JSON_spec_fixed_auto (
          "merchant_pub",
          &orr.details.ok.merchant_pub),
        GNUNET_JSON_spec_end ()
      };

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

        memset (rds,
                0,
                sizeof (rds));
        for (unsigned int i = 0; i < refund_len; i++)
        {
          struct TALER_MERCHANT_PostOrdersRefundDetail *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->exchange_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);
            orr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
            orr.hr.http_status = 0;
            goto finish;
          }

          rd->exchange_http_status = exchange_status;
          rd->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->exchange_sig),
                GNUNET_JSON_spec_fixed_auto ("exchange_pub",
                                             &rd->exchange_pub),
                GNUNET_JSON_spec_end ()
              };

              if (GNUNET_OK !=
                  GNUNET_JSON_parse (jrefund,
                                     rspec,
                                     NULL,
                                     NULL))
              {
                GNUNET_break_op (0);
                orr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
                orr.hr.http_status = 0;
                goto finish;
              }
              if (0 != strcmp ("success",
                               refund_status_type))
              {
                GNUNET_break_op (0);
                orr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
                orr.hr.http_status = 0;
                goto finish;
              }
            }
            break;
          default:
            if (0 != strcmp ("failure",
                             refund_status_type))
            {
              GNUNET_break_op (0);
              orr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
              orr.hr.http_status = 0;
              goto finish;
            }
          }
        }

        orr.details.ok.refunds = rds;
        orr.details.ok.num_refunds = refund_len;
        porh->cb (porh->cb_cls,
                  &orr);
        TALER_MERCHANT_post_orders_refund_cancel (porh);
        return;
      }
    }
    break;
  case MHD_HTTP_NO_CONTENT:
    break;
  case MHD_HTTP_FORBIDDEN:
    orr.hr.ec = TALER_JSON_get_error_code (json);
    orr.hr.hint = TALER_JSON_get_error_hint (json);
    break;
  case MHD_HTTP_NOT_FOUND:
    orr.hr.ec = TALER_JSON_get_error_code (json);
    orr.hr.hint = TALER_JSON_get_error_hint (json);
    break;
  case MHD_HTTP_GONE:
    orr.hr.ec = TALER_JSON_get_error_code (json);
    orr.hr.hint = TALER_JSON_get_error_hint (json);
    break;
  case MHD_HTTP_CONTENT_TOO_LARGE:
    orr.hr.ec = TALER_JSON_get_error_code (json);
    orr.hr.hint = TALER_JSON_get_error_hint (json);
    break;
  case MHD_HTTP_INTERNAL_SERVER_ERROR:
    orr.hr.ec = TALER_JSON_get_error_code (json);
    orr.hr.hint = TALER_JSON_get_error_hint (json);
    break;
  default:
    GNUNET_break_op (0);
    TALER_MERCHANT_parse_error_details_ (json,
                                         response_code,
                                         &orr.hr);
    break;
  }
finish:
  porh->cb (porh->cb_cls,
            &orr);
  TALER_MERCHANT_post_orders_refund_cancel (porh);
}


struct TALER_MERCHANT_PostOrdersRefundHandle *
TALER_MERCHANT_post_orders_refund_create (
  struct GNUNET_CURL_Context *ctx,
  const char *url,
  const char *order_id,
  const struct TALER_PrivateContractHashP *h_contract_terms)
{
  struct TALER_MERCHANT_PostOrdersRefundHandle *porh;

  porh = GNUNET_new (struct TALER_MERCHANT_PostOrdersRefundHandle);
  porh->ctx = ctx;
  porh->base_url = GNUNET_strdup (url);
  porh->order_id = GNUNET_strdup (order_id);
  porh->h_contract_terms = *h_contract_terms;
  return porh;
}


enum TALER_ErrorCode
TALER_MERCHANT_post_orders_refund_start (
  struct TALER_MERCHANT_PostOrdersRefundHandle *porh,
  TALER_MERCHANT_PostOrdersRefundCallback cb,
  TALER_MERCHANT_POST_ORDERS_REFUND_RESULT_CLOSURE *cb_cls)
{
  json_t *req_obj;
  CURL *eh;

  porh->cb = cb;
  porh->cb_cls = cb_cls;
  {
    char *path;

    GNUNET_asprintf (&path,
                     "orders/%s/refund",
                     porh->order_id);
    porh->url = TALER_url_join (porh->base_url,
                                path,
                                NULL);
    GNUNET_free (path);
  }
  if (NULL == porh->url)
    return TALER_EC_GENERIC_CONFIGURATION_INVALID;
  req_obj = GNUNET_JSON_PACK (
    GNUNET_JSON_pack_data_auto ("h_contract",
                                &porh->h_contract_terms));
  eh = TALER_MERCHANT_curl_easy_get_ (porh->url);
  if ( (NULL == eh) ||
       (GNUNET_OK !=
        TALER_curl_easy_post (&porh->post_ctx,
                              eh,
                              req_obj)) )
  {
    GNUNET_break (0);
    json_decref (req_obj);
    if (NULL != eh)
      curl_easy_cleanup (eh);
    return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
  }
  json_decref (req_obj);
  porh->job = GNUNET_CURL_job_add2 (porh->ctx,
                                    eh,
                                    porh->post_ctx.headers,
                                    &handle_refund_finished,
                                    porh);
  if (NULL == porh->job)
    return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
  return TALER_EC_NONE;
}


void
TALER_MERCHANT_post_orders_refund_cancel (
  struct TALER_MERCHANT_PostOrdersRefundHandle *porh)
{
  if (NULL != porh->job)
  {
    GNUNET_CURL_job_cancel (porh->job);
    porh->job = NULL;
  }
  TALER_curl_easy_post_finished (&porh->post_ctx);
  GNUNET_free (porh->order_id);
  GNUNET_free (porh->url);
  GNUNET_free (porh->base_url);
  GNUNET_free (porh);
}


/* end of merchant_api_post-orders-ORDER_ID-refund-new.c */
