/*
  This file is part of TALER
  Copyright (C) 2025 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/testing_api_cmd_wallet_get_template.c
 * @brief command to test GET /templates/$ID (wallet)
 * @author Bohdan Potuzhnyi
 */
#include "taler/platform.h"
#include <microhttpd.h>
#include <jansson.h>
#include <taler/taler_testing_lib.h>
#include "taler/taler_merchant_service.h"
#include "taler/taler_merchant_testing_lib.h"

/**
 * State of a "GET template" wallet CMD.
 */
struct WalletGetTemplateState
{
  /**
   * Handle for a "GET template" request.
   */
  struct TALER_MERCHANT_WalletTemplateGetHandle *igh;

  /**
   * The interpreter state.
   */
  struct TALER_TESTING_Interpreter *is;

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

  /**
   * ID of the template to run GET for.
   */
  const char *template_id;

  /**
   * Expected product count (0 to ignore).
   */
  size_t expected_products_len;

  /**
   * Product id to verify unit info for (optional).
   */
  const char *expected_product_id;

  /**
   * Expected unit for @e expected_product_id.
   */
  const char *expected_unit;

  /**
   * Expected allow_fraction for @e expected_product_id.
   */
  bool expected_unit_allow_fraction;

  /**
   * Expected precision for @e expected_product_id.
   */
  uint32_t expected_unit_precision_level;

  /**
   * Expected unit name short i18n for @e expected_unit.
   */
  json_t *expected_unit_name_short_i18n;

  /**
   * Optional second product id expected to be present.
   */
  const char *expected_product_id2;

  /**
   * Optional third product id expected to be present.
   */
  const char *expected_product_id3;

  /**
   * Expected category id 1 (0 to ignore).
   */
  uint64_t expected_category_id1;

  /**
   * Expected category id 2 (0 to ignore).
   */
  uint64_t expected_category_id2;

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

static bool
product_id_matches (const json_t *product,
                    const char *expected_id)
{
  const json_t *id_val;

  if (NULL == expected_id)
    return false;
  id_val = json_object_get (product,
                            "product_id");
  if (! json_is_string (id_val))
    return false;
  return (0 == strcmp (json_string_value (id_val),
                       expected_id));
}


/**
 * Callback for a wallet /get/templates/$ID operation.
 *
 * @param cls closure for this function
 * @param tgr HTTP response details
 */
static void
wallet_get_template_cb (void *cls,
                        const struct
                        TALER_MERCHANT_WalletTemplateGetResponse *tgr)
{
  struct WalletGetTemplateState *wgs = cls;

  wgs->igh = NULL;
  if (wgs->http_status != tgr->hr.http_status)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unexpected response code %u (%d) to command %s\n",
                tgr->hr.http_status,
                (int) tgr->hr.ec,
                TALER_TESTING_interpreter_get_current_label (wgs->is));
    TALER_TESTING_interpreter_fail (wgs->is);
    return;
  }
  if (MHD_HTTP_OK == tgr->hr.http_status)
  {
    const json_t *template_contract = tgr->details.ok.template_contract;
    const json_t *inventory_payload;
    const json_t *products;

    inventory_payload = json_object_get (template_contract,
                                         "inventory_payload");
    if (! json_is_object (inventory_payload))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Missing inventory_payload in wallet template\n");
      TALER_TESTING_interpreter_fail (wgs->is);
      return;
    }
    products = json_object_get (inventory_payload,
                                "products");
    if (! json_is_array (products))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Missing products in wallet template\n");
      TALER_TESTING_interpreter_fail (wgs->is);
      return;
    }
    if ( (0 < wgs->expected_products_len) &&
         (json_array_size (products) != wgs->expected_products_len) )
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Unexpected products length\n");
      TALER_TESTING_interpreter_fail (wgs->is);
      return;
    }

    {
      bool found_unit = (NULL == wgs->expected_product_id);
      bool found_extra = (NULL == wgs->expected_product_id2);
      bool found_extra2 = (NULL == wgs->expected_product_id3);

      for (size_t i = 0; i < json_array_size (products); i++)
      {
        const json_t *product = json_array_get (products,
                                                i);

        if (product_id_matches (product,
                                wgs->expected_product_id))
        {
          const json_t *unit_val;
          const json_t *allow_val;
          const json_t *prec_val;

          unit_val = json_object_get (product,
                                      "unit");
          allow_val = json_object_get (product,
                                       "unit_allow_fraction");
          prec_val = json_object_get (product,
                                      "unit_precision_level");
          if ( (NULL == unit_val) ||
               (! json_is_string (unit_val)) ||
               (0 != strcmp (json_string_value (unit_val),
                             wgs->expected_unit)) )
          {
            GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                        "Unexpected unit in wallet template\n");
            TALER_TESTING_interpreter_fail (wgs->is);
            return;
          }
          if ( (! json_is_boolean (allow_val)) ||
               (json_boolean_value (allow_val) !=
                wgs->expected_unit_allow_fraction) )
          {
            GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                        "Unexpected unit_allow_fraction in wallet template\n");
            TALER_TESTING_interpreter_fail (wgs->is);
            return;
          }
          if ( (! json_is_integer (prec_val)) ||
               ((uint32_t) json_integer_value (prec_val) !=
                wgs->expected_unit_precision_level) )
          {
            GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                        "Unexpected unit_precision_level in wallet template\n");
            TALER_TESTING_interpreter_fail (wgs->is);
            return;
          }
          found_unit = true;
        }
        if (product_id_matches (product,
                                wgs->expected_product_id2))
          found_extra = true;
        if (product_id_matches (product,
                                wgs->expected_product_id3))
          found_extra2 = true;
      }
      if (! found_unit || ! found_extra || ! found_extra2)
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Expected product ids missing in wallet template\n");
        TALER_TESTING_interpreter_fail (wgs->is);
        return;
      }
    }

    if (NULL != wgs->expected_unit_name_short_i18n)
    {
      const json_t *units;
      bool found_unit_i18n = false;

      units = json_object_get (inventory_payload,
                               "units");
      if (! json_is_array (units))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Missing units in wallet template\n");
        TALER_TESTING_interpreter_fail (wgs->is);
        return;
      }
      for (size_t i = 0; i < json_array_size (units); i++)
      {
        const json_t *unit = json_array_get (units,
                                             i);
        const json_t *unit_id;
        const json_t *unit_i18n;

        unit_id = json_object_get (unit,
                                   "unit");
        if (! json_is_string (unit_id))
          continue;
        if (0 != strcmp (json_string_value (unit_id),
                         wgs->expected_unit))
          continue;
        unit_i18n = json_object_get (unit,
                                     "unit_name_short_i18n");
        if ( (NULL == unit_i18n) ||
             (! json_is_object (unit_i18n)) ||
             (1 != json_equal (unit_i18n,
                               wgs->expected_unit_name_short_i18n)) )
        {
          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                      "Unexpected unit_name_short_i18n in wallet template\n");
          TALER_TESTING_interpreter_fail (wgs->is);
          return;
        }
        found_unit_i18n = true;
        break;
      }
      if (! found_unit_i18n)
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Expected unit entry missing in wallet template\n");
        TALER_TESTING_interpreter_fail (wgs->is);
        return;
      }
    }

    if ( (0 != wgs->expected_category_id1) ||
         (0 != wgs->expected_category_id2) )
    {
      const json_t *categories;
      bool found_cat1 = (0 == wgs->expected_category_id1);
      bool found_cat2 = (0 == wgs->expected_category_id2);

      categories = json_object_get (inventory_payload,
                                    "categories");
      if (! json_is_array (categories))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Missing categories in wallet template\n");
        TALER_TESTING_interpreter_fail (wgs->is);
        return;
      }
      for (size_t i = 0; i < json_array_size (categories); i++)
      {
        const json_t *category = json_array_get (categories,
                                                 i);
        const json_t *cid;

        cid = json_object_get (category,
                               "category_id");
        if (! json_is_integer (cid))
          continue;
        if ( (0 != wgs->expected_category_id1) &&
             ((uint64_t) json_integer_value (cid)
              == wgs->expected_category_id1) )
          found_cat1 = true;
        if ( (0 != wgs->expected_category_id2) &&
             ((uint64_t) json_integer_value (cid)
              == wgs->expected_category_id2) )
          found_cat2 = true;
      }
      if (! found_cat1 || ! found_cat2)
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Expected category ids missing in wallet template\n");
        TALER_TESTING_interpreter_fail (wgs->is);
        return;
      }
    }
  }
  TALER_TESTING_interpreter_next (wgs->is);
}


/**
 * Run the "GET /templates/$ID" wallet CMD.
 *
 * @param cls closure.
 * @param cmd command being run now.
 * @param is interpreter state.
 */
static void
wallet_get_template_run (void *cls,
                         const struct TALER_TESTING_Command *cmd,
                         struct TALER_TESTING_Interpreter *is)
{
  struct WalletGetTemplateState *wgs = cls;

  wgs->is = is;
  wgs->igh = TALER_MERCHANT_wallet_template_get (
    TALER_TESTING_interpreter_get_context (is),
    wgs->merchant_url,
    wgs->template_id,
    &wallet_get_template_cb,
    wgs);
  GNUNET_assert (NULL != wgs->igh);
}


/**
 * Free the state of a "GET template" CMD, and possibly
 * cancel a pending operation thereof.
 *
 * @param cls closure.
 * @param cmd command being run.
 */
static void
wallet_get_template_cleanup (void *cls,
                             const struct TALER_TESTING_Command *cmd)
{
  struct WalletGetTemplateState *wgs = cls;

  if (NULL != wgs->igh)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "GET /templates/$ID operation did not complete\n");
    TALER_MERCHANT_wallet_template_get_cancel (wgs->igh);
  }
  json_decref (wgs->expected_unit_name_short_i18n);
  GNUNET_free (wgs);
}


struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_wallet_get_template (
  const char *label,
  const char *merchant_url,
  const char *template_id,
  size_t expected_products_len,
  const char *expected_product_id,
  const char *expected_unit,
  bool expected_unit_allow_fraction,
  uint32_t expected_unit_precision_level,
  json_t *expected_unit_name_short_i18n,
  const char *expected_product_id2,
  const char *expected_product_id3,
  uint64_t expected_category_id1,
  uint64_t expected_category_id2,
  unsigned int http_status)
{
  struct WalletGetTemplateState *wgs;

  wgs = GNUNET_new (struct WalletGetTemplateState);
  wgs->merchant_url = merchant_url;
  wgs->template_id = template_id;
  wgs->expected_products_len = expected_products_len;
  wgs->expected_product_id = expected_product_id;
  wgs->expected_unit = expected_unit;
  wgs->expected_unit_allow_fraction = expected_unit_allow_fraction;
  wgs->expected_unit_precision_level = expected_unit_precision_level;
  if (NULL != expected_unit_name_short_i18n)
    wgs->expected_unit_name_short_i18n =
      json_incref (expected_unit_name_short_i18n);
  wgs->expected_product_id2 = expected_product_id2;
  wgs->expected_product_id3 = expected_product_id3;
  wgs->expected_category_id1 = expected_category_id1;
  wgs->expected_category_id2 = expected_category_id2;
  wgs->http_status = http_status;
  {
    struct TALER_TESTING_Command cmd = {
      .cls = wgs,
      .label = label,
      .run = &wallet_get_template_run,
      .cleanup = &wallet_get_template_cleanup
    };

    return cmd;
  }
}


/* end of testing_api_cmd_wallet_get_template.c */
