/*
  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_api_cmd_get_units.c
 * @brief command to test GET /private/units
 * @author Bohdan Potuzhnyi
 */
#include "platform.h"
#include <jansson.h>
#include <taler/taler_testing_lib.h>
#include "taler_merchant_service.h"
#include "taler_merchant_testing_lib.h"


/**
 * State for a GET /private/units command.
 */
struct GetUnitsState
{
  /**
   * In-flight request handle.
   */
  struct TALER_MERCHANT_UnitsGetHandle *ugh;

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

  /**
   * Merchant backend base URL.
   */
  const char *merchant_url;

  /**
   * Expected HTTP status.
   */
  unsigned int http_status;

  /**
   * Expected references that must appear in the listing.
   */
  const char **references;

  /**
   * Length of @e references.
   */
  unsigned int references_length;
};


/**
 * Verify that @a entry matches the traits from @a ref_cmd.
 */
static enum GNUNET_GenericReturnValue
check_unit_matches (const struct TALER_MERCHANT_UnitEntry *entry,
                    const struct TALER_TESTING_Command *ref_cmd)
{
  const char *unit_id = NULL;

  if (GNUNET_OK !=
      TALER_TESTING_get_trait_unit_id (ref_cmd,
                                       &unit_id))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Internal error: command `%s' lacks unit_id trait\n",
                ref_cmd->label);
    return GNUNET_SYSERR;
  }
  if (0 != strcmp (entry->unit,
                   unit_id))
    return GNUNET_NO;

  {
    const char *unit_name_long = NULL;

    if (GNUNET_OK ==
        TALER_TESTING_get_trait_unit_name_long (ref_cmd,
                                                &unit_name_long))
    {
      if ( (NULL != unit_name_long) &&
           (0 != strcmp (entry->unit_name_long,
                         unit_name_long)) )
        return GNUNET_SYSERR;
    }
  }
  {
    const char *unit_name_short = NULL;

    if (GNUNET_OK ==
        TALER_TESTING_get_trait_unit_name_short (ref_cmd,
                                                 &unit_name_short))
    {
      if ( (NULL != unit_name_short) &&
           (0 != strcmp (entry->unit_name_short,
                         unit_name_short)) )
        return GNUNET_SYSERR;
    }
  }
  {
    const bool *unit_allow_fraction = NULL;

    if (GNUNET_OK ==
        TALER_TESTING_get_trait_unit_allow_fraction (ref_cmd,
                                                     &unit_allow_fraction))
    {
      if ( (NULL != unit_allow_fraction) &&
           (*unit_allow_fraction != entry->unit_allow_fraction) )
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Unit %s mismatch: expected allow_fraction %d got %d\n",
                    entry->unit,
                    (int) *unit_allow_fraction,
                    (int) entry->unit_allow_fraction);
        return GNUNET_SYSERR;
      }
    }
  }
  {
    const uint32_t *unit_precision_level = NULL;

    if (GNUNET_OK ==
        TALER_TESTING_get_trait_unit_precision_level (ref_cmd,
                                                      &unit_precision_level))
    {
      if ( (NULL != unit_precision_level) &&
           (*unit_precision_level != entry->unit_precision_level) )
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Unit %s mismatch: expected precision %u got %u\n",
                    entry->unit,
                    (unsigned int) *unit_precision_level,
                    (unsigned int) entry->unit_precision_level);
        return GNUNET_SYSERR;
      }
    }
  }
  {
    const bool *unit_active = NULL;

    if (GNUNET_OK ==
        TALER_TESTING_get_trait_unit_active (ref_cmd,
                                             &unit_active))
    {
      if ( (NULL != unit_active) &&
           (*unit_active != entry->unit_active) )
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Unit %s mismatch: expected active %d got %d\n",
                    entry->unit,
                    (int) *unit_active,
                    (int) entry->unit_active);
        return GNUNET_SYSERR;
      }
    }
  }
  {
    const json_t *unit_name_long_i18n = NULL;

    if (GNUNET_OK ==
        TALER_TESTING_get_trait_unit_name_long_i18n (ref_cmd,
                                                     &unit_name_long_i18n))
    {
      if ( (NULL != unit_name_long_i18n) &&
           ( (NULL == entry->unit_name_long_i18n) ||
             (1 != json_equal (unit_name_long_i18n,
                               entry->unit_name_long_i18n)) ) )
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Unit %s mismatch: long_i18n differs\n",
                    entry->unit);
        return GNUNET_SYSERR;
      }
    }
  }
  {
    const json_t *unit_name_short_i18n = NULL;

    if (GNUNET_OK ==
        TALER_TESTING_get_trait_unit_name_short_i18n (ref_cmd,
                                                      &unit_name_short_i18n))
    {
      if ( (NULL != unit_name_short_i18n) &&
           ( (NULL == entry->unit_name_short_i18n) ||
             (1 != json_equal (unit_name_short_i18n,
                               entry->unit_name_short_i18n)) ) )
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Unit %s mismatch: short_i18n differs\n",
                    entry->unit);
        return GNUNET_SYSERR;
      }
    }
  }
  return GNUNET_OK;
}


/**
 * Completion callback for GET /private/units.
 */
static void
get_units_cb (void *cls,
              const struct TALER_MERCHANT_UnitsGetResponse *ugr)
{
  struct GetUnitsState *gus = cls;

  gus->ugh = NULL;
  if (gus->http_status != ugr->hr.http_status)
  {
    TALER_TESTING_unexpected_status_with_body (gus->is,
                                               ugr->hr.http_status,
                                               gus->http_status,
                                               ugr->hr.reply);
    return;
  }
  if (MHD_HTTP_OK == ugr->hr.http_status)
  {
    for (unsigned int i = 0; i < gus->references_length; ++i)
    {
      const char *label = gus->references[i];
      const struct TALER_TESTING_Command *ref_cmd;
      enum GNUNET_GenericReturnValue match = GNUNET_NO;

      ref_cmd = TALER_TESTING_interpreter_lookup_command (gus->is,
                                                          label);
      if (NULL == ref_cmd)
      {
        GNUNET_break (0);
        TALER_TESTING_interpreter_fail (gus->is);
        return;
      }
      for (unsigned int j = 0;
           j < ugr->details.ok.units_length;
           ++j)
      {
        match = check_unit_matches (&ugr->details.ok.units[j],
                                    ref_cmd);
        if (GNUNET_SYSERR == match)
          break;
        if (GNUNET_OK == match)
          break;
      }
      if (GNUNET_SYSERR == match)
      {
        GNUNET_break (0);
        TALER_TESTING_interpreter_fail (gus->is);
        return;
      }
      if (GNUNET_OK != match)
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Unit referenced by `%s' not found in GET /private/units response\n",
                    label);
        TALER_TESTING_interpreter_fail (gus->is);
        return;
      }
    }
  }
  TALER_TESTING_interpreter_next (gus->is);
}


/**
 * Issue the GET request.
 */
static void
get_units_run (void *cls,
               const struct TALER_TESTING_Command *cmd,
               struct TALER_TESTING_Interpreter *is)
{
  struct GetUnitsState *gus = cls;

  gus->is = is;
  gus->ugh = TALER_MERCHANT_units_get (
    TALER_TESTING_interpreter_get_context (is),
    gus->merchant_url,
    &get_units_cb,
    gus);
  if (NULL == gus->ugh)
  {
    GNUNET_break (0);
    TALER_TESTING_interpreter_fail (is);
  }
}


/**
 * Cleanup.
 */
static void
get_units_cleanup (void *cls,
                   const struct TALER_TESTING_Command *cmd)
{
  struct GetUnitsState *gus = cls;

  if (NULL != gus->ugh)
  {
    TALER_MERCHANT_units_get_cancel (gus->ugh);
    gus->ugh = NULL;
  }
  GNUNET_array_grow (gus->references,
                     gus->references_length,
                     0);
  GNUNET_free (gus);
}


struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_get_units (const char *label,
                                      const char *merchant_url,
                                      unsigned int http_status,
                                      ...)
{
  struct GetUnitsState *gus;
  va_list ap;
  const char *ref;

  gus = GNUNET_new (struct GetUnitsState);
  gus->merchant_url = merchant_url;
  gus->http_status = http_status;

  va_start (ap, http_status);
  while (NULL != (ref = va_arg (ap, const char *)))
  {
    GNUNET_array_append (gus->references,
                         gus->references_length,
                         ref);
  }
  va_end (ap);

  {
    struct TALER_TESTING_Command cmd = {
      .cls = gus,
      .label = label,
      .run = &get_units_run,
      .cleanup = &get_units_cleanup
    };

    return cmd;
  }
}


/* end of testing_api_cmd_get_units.c */
