/*
  This file is part of GNU 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 util/contract_serialize.c
 * @brief shared logic for contract terms serialization
 * @author Iván Ávalos
 */

#include "platform.h"
#include <gnunet/gnunet_json_lib.h>
#include <gnunet/gnunet_common.h>
#include <taler/taler_json_lib.h>
#include <jansson.h>
#include "taler/taler_util.h"
#include "taler_merchant_util.h"

/**
 * Get JSON representation of merchant details.
 *
 * @param[in] contract contract terms
 * @return JSON object with merchant details; NULL on error
 */
static json_t *
json_from_merchant_details (
  const struct TALER_MERCHANT_Contract *contract)
{
  return GNUNET_JSON_PACK (
    GNUNET_JSON_pack_string ("name",
                             contract->merchant.name),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_string ("email",
                               contract->merchant.email)),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_string ("website",
                               contract->merchant.website)),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_string ("logo",
                               contract->merchant.logo)),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_object_steal ("address",
                                     contract->merchant.address)),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_object_steal ("jurisdiction",
                                     contract->merchant.jurisdiction)));
}


/**
 * Get JSON representation of contract choice input.
 *
 * @param[in] input contract terms choice input
 * @return JSON representation of @a input; NULL on error
 */
static json_t *
json_from_contract_input (
  const struct TALER_MERCHANT_ContractInput *input)
{
  switch (input->type)
  {
  case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID:
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "invalid contract input type");
    GNUNET_assert (0);
    return NULL;
  case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN:
    return GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("type",
                               "token"),
      GNUNET_JSON_pack_string ("token_family_slug",
                               input->details.token.token_family_slug),
      GNUNET_JSON_pack_int64 ("count",
                              input->details.token.count));
  }

  GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
              "unsupported contract input type %d",
              input->type);
  GNUNET_assert (0);
  return NULL;
}


/**
 * Get JSON representation of contract choice output.
 *
 * @param[in] output contract terms choice output
 * @return JSON representation of @a output; NULL on error
 */
static json_t *
json_from_contract_output (
  const struct TALER_MERCHANT_ContractOutput *output)
{
  switch (output->type)
  {
  case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "invalid contract output type");
    GNUNET_assert (0);
    return NULL;
  case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
    return GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("type",
                               "token"),
      GNUNET_JSON_pack_string ("token_family_slug",
                               output->details.token.token_family_slug),
      GNUNET_JSON_pack_uint64 ("count",
                               output->details.token.count),
      GNUNET_JSON_pack_uint64 ("key_index",
                               output->details.token.key_index));
  case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
    {
      json_t *donau_urls;

      donau_urls = json_array ();
      GNUNET_assert (NULL != donau_urls);
      for (unsigned i = 0;
           i < output->details.donation_receipt.donau_urls_len;
           i++)
        GNUNET_assert (0 ==
                       json_array_append_new (
                         donau_urls,
                         json_string (
                           output->details.donation_receipt.donau_urls[i])));

      return GNUNET_JSON_PACK (
        GNUNET_JSON_pack_string ("type",
                                 "tax-receipt"),
        GNUNET_JSON_pack_array_steal ("donau_urls",
                                      donau_urls),
        GNUNET_JSON_pack_allow_null (
          TALER_JSON_pack_amount ("amount",
                                  &output->details.donation_receipt.amount)));
    }
  }
  GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
              "Unsupported contract output type %d",
              output->type);
  GNUNET_assert (0);
  return NULL;
}


json_t *
TALER_MERCHANT_json_from_contract_choice (
  const struct TALER_MERCHANT_ContractChoice *choice,
  bool order)
{
  json_t *inputs;
  json_t *outputs;

  inputs = json_array ();
  GNUNET_assert (NULL != inputs);
  for (unsigned int i = 0; i < choice->inputs_len; i++)
    GNUNET_assert (0 ==
                   json_array_append_new (inputs,
                                          json_from_contract_input (
                                            &choice->inputs[i])));
  outputs = json_array ();
  GNUNET_assert (NULL != outputs);
  for (unsigned int i = 0; i < choice->outputs_len; i++)
    GNUNET_assert (0 ==
                   json_array_append_new (outputs,
                                          json_from_contract_output (
                                            &choice->outputs[i])));

  return GNUNET_JSON_PACK (
    TALER_JSON_pack_amount ("amount",
                            &choice->amount),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_string ("description",
                               choice->description)),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_object_incref ("description_i18n",
                                      choice->description_i18n)),
    (order)
    ? GNUNET_JSON_pack_allow_null (
      TALER_JSON_pack_amount (
        "max_fee",
        /* workaround for nullable amount */
        (GNUNET_OK ==
         TALER_amount_is_valid (&choice->max_fee))
        ? &choice->max_fee
        : NULL))
    : TALER_JSON_pack_amount ("max_fee",
                              &choice->max_fee),
    (order)
    ? GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_array_steal ("inputs",
                                    inputs))
    : GNUNET_JSON_pack_array_steal ("inputs",
                                    inputs),
    (order)
    ? GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_array_steal ("outputs",
                                    outputs))
    : GNUNET_JSON_pack_array_steal ("outputs",
                                    outputs));
}


/**
 * Get JSON representation of contract token family key.
 *
 * @param[in] key contract token family key
 * @return JSON representation of @a key; NULL on error
 */
static json_t *
json_from_token_family_key (
  const struct TALER_MERCHANT_ContractTokenFamilyKey *key)
{
  return GNUNET_JSON_PACK (
    GNUNET_JSON_pack_timestamp ("signature_validity_start",
                                key->valid_after),
    GNUNET_JSON_pack_timestamp ("signature_validity_end",
                                key->valid_before),
    TALER_JSON_pack_token_pub (NULL,
                               &key->pub));
}


/**
 * Get JSON representation of contract token family details.
 *
 * @param[in] family contract token family
 * @return JSON representation of @a family->details; NULL on error
 */
static json_t *
json_from_token_family_details (
  const struct TALER_MERCHANT_ContractTokenFamily *family)
{
  switch (family->kind)
  {
  case TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID:
    break;
  case TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION:
    {
      json_t *trusted_domains;

      trusted_domains = json_array ();
      GNUNET_assert (NULL != trusted_domains);
      for (unsigned int i = 0;
           i < family->details.subscription.trusted_domains_len;
           i++)
        GNUNET_assert (0 ==
                       json_array_append_new (
                         trusted_domains,
                         json_string (
                           family->details.subscription.trusted_domains[i])));

      return GNUNET_JSON_PACK (
        GNUNET_JSON_pack_string ("class",
                                 "subscription"),
        GNUNET_JSON_pack_array_steal ("trusted_domains",
                                      trusted_domains));
    }
  case TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT:
    {
      json_t *expected_domains;

      expected_domains = json_array ();
      GNUNET_assert (NULL != expected_domains);
      for (unsigned int i = 0;
           i < family->details.discount.expected_domains_len;
           i++)
        GNUNET_assert (0 ==
                       json_array_append_new (
                         expected_domains,
                         json_string (
                           family->details.discount.expected_domains[i])));

      return GNUNET_JSON_PACK (
        GNUNET_JSON_pack_string ("class",
                                 "discount"),
        GNUNET_JSON_pack_array_steal ("expected_domains",
                                      expected_domains));
    }
  }

  GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
              "unsupported token family kind %d",
              family->kind);
  GNUNET_assert (0);
  return NULL;
}


json_t *
TALER_MERCHANT_json_from_token_family (
  const struct TALER_MERCHANT_ContractTokenFamily *family)
{
  json_t *keys;

  keys = json_array ();
  GNUNET_assert (NULL != keys);
  for (unsigned int i = 0; i < family->keys_len; i++)
    GNUNET_assert (0 == json_array_append_new (
                     keys,
                     json_from_token_family_key (
                       &family->keys[i])));

  return GNUNET_JSON_PACK (
    GNUNET_JSON_pack_string ("name",
                             family->name),
    GNUNET_JSON_pack_string ("description",
                             family->description),
    GNUNET_JSON_pack_object_incref ("description_i18n",
                                    family->description_i18n),
    GNUNET_JSON_pack_array_steal ("keys",
                                  keys),
    GNUNET_JSON_pack_object_steal ("details",
                                   json_from_token_family_details (family)),
    GNUNET_JSON_pack_bool ("critical",
                           family->critical));
}


/**
 * Get JSON object with contract terms v0-specific fields.
 *
 * @param[in] input contract terms v0
 * @return JSON object with @a input v0 fields; NULL on error
 */
static json_t *
json_from_contract_v0 (
  const struct TALER_MERCHANT_Contract *input)
{
  return GNUNET_JSON_PACK (
    TALER_JSON_pack_amount ("amount",
                            &input->details.v0.brutto),
    TALER_JSON_pack_amount ("max_fee",
                            &input->details.v0.max_fee));
}


/**
 * Get JSON object with contract terms v1-specific fields.
 *
 * @param[in] input contract terms v1
 * @return JSON object with @a input v1 fields; NULL on error
 */
static json_t *
json_from_contract_v1 (
  const struct TALER_MERCHANT_Contract *input)
{
  json_t *choices;
  json_t *families;

  choices = json_array ();
  GNUNET_assert (0 != choices);
  for (unsigned i = 0; i < input->details.v1.choices_len; i++)
    GNUNET_assert (0 == json_array_append_new (
                     choices,
                     TALER_MERCHANT_json_from_contract_choice (
                       &input->details.v1.choices[i],
                       false)));

  families = json_object ();
  GNUNET_assert (0 != families);
  for (unsigned i = 0; i < input->details.v1.token_authorities_len; i++)
    GNUNET_assert (0 == json_object_set_new (
                     families,
                     input->details.v1.token_authorities[i].slug,
                     TALER_MERCHANT_json_from_token_family (
                       &input->details.v1.token_authorities[i])));

  return GNUNET_JSON_PACK (
    GNUNET_JSON_pack_array_steal ("choices",
                                  choices),
    GNUNET_JSON_pack_object_steal ("token_families",
                                   families));
}


json_t *
TALER_MERCHANT_contract_serialize (
  const struct TALER_MERCHANT_Contract *input,
  bool nonce_optional)
{
  json_t *details;

  switch (input->version)
  {
  case TALER_MERCHANT_CONTRACT_VERSION_0:
    details = json_from_contract_v0 (input);
    goto success;
  case TALER_MERCHANT_CONTRACT_VERSION_1:
    details = json_from_contract_v1 (input);
    goto success;
  }

  GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
              "unknown contract type version %d",
              input->version);
  GNUNET_assert (0);
  return NULL;

success:
  return GNUNET_JSON_PACK (
    GNUNET_JSON_pack_uint64 ("version",
                             input->version),
    GNUNET_JSON_pack_string ("summary",
                             input->summary),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_object_steal ("summary_i18n",
                                     input->summary_i18n)),
    GNUNET_JSON_pack_string ("order_id",
                             input->order_id),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_string ("public_reorder_url",
                               input->public_reorder_url)),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_string ("fulfillment_url",
                               input->fulfillment_url)),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_string ("fulfillment_message",
                               input->fulfillment_message)),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_object_steal ("fulfillment_message_i18n",
                                     input->fulfillment_message_i18n)),
    GNUNET_JSON_pack_array_steal ("products",
                                  input->products),
    GNUNET_JSON_pack_timestamp ("timestamp",
                                input->timestamp),
    GNUNET_JSON_pack_timestamp ("refund_deadline",
                                input->refund_deadline),
    GNUNET_JSON_pack_timestamp ("pay_deadline",
                                input->pay_deadline),
    GNUNET_JSON_pack_timestamp ("wire_transfer_deadline",
                                input->wire_deadline),
    GNUNET_JSON_pack_data_auto ("merchant_pub",
                                &input->merchant_pub.eddsa_pub),
    GNUNET_JSON_pack_string ("merchant_base_url",
                             input->merchant_base_url),
    GNUNET_JSON_pack_object_steal ("merchant",
                                   json_from_merchant_details (input)),
    GNUNET_JSON_pack_data_auto ("h_wire",
                                &input->h_wire),
    GNUNET_JSON_pack_string ("wire_method",
                             input->wire_method),
    GNUNET_JSON_pack_array_steal ("exchanges",
                                  input->exchanges),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_object_steal ("delivery_location",
                                     input->delivery_location)),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_timestamp ("delivery_date",
                                  input->delivery_date)),
    (nonce_optional)
    ? GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_string ("nonce",
                               input->nonce))
    : GNUNET_JSON_pack_string ("nonce",
                               input->nonce),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_time_rel ("auto_refund",
                                 input->auto_refund)),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_object_steal ("extra",
                                     input->extra)),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_uint64 ("minimum_age",
                               input->minimum_age)),
    GNUNET_JSON_pack_object_steal (NULL,
                                   details));
}
