/*
  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
  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_charity_patch.c
 * @brief Implement the PATCH /charities/$ID test command.
 * @author Bohdan Potuzhnyi
 */
#include <donau_config.h>
#include <taler/taler_json_lib.h>
#include <gnunet/gnunet_curl_lib.h>
#include <taler/taler_testing_lib.h>
#include "donau_testing_lib.h"


/**
 * Command state for PATCH /charities/$ID.
 */
struct CharityPatchState
{
  /**
   * Handle for the in-flight PATCH request (if any).
   */
  struct DONAU_CharityPatchHandle *cph;

  /**
   * Reference label of the command that created the original charity.
   */
  const char *charity_reference;

  /**
   * Fresh charity key pair generated for the update.
   */
  struct DONAU_CharityPrivateKeyP charity_priv;

  /**
   * Corresponding public key transmitted in the PATCH body.
   */
  struct DONAU_CharityPublicKeyP charity_pub;

  /**
   * Updated yearly donation limit.
   */
  struct TALER_Amount max_per_year;

  /**
   * Updated human-readable name.
   */
  const char *charity_name;

  /**
   * Updated contact URL.
   */
  const char *charity_url;

  /**
   * Administrator bearer token used for authentication.
   */
  const struct DONAU_BearerToken *bearer;

  /**
   * Expected HTTP status code for the PATCH response.
   */
  unsigned int expected_response_code;

  /**
   * Database identifier of the charity being updated.
   */
  uint64_t charity_id;

  /**
   * Interpreter instance driving the command sequence.
   */
  struct TALER_TESTING_Interpreter *is;
};


/**
 * Check HTTP response for the PATCH request and advance the interpreter on success.
 *
 * @param cls closure
 * @param resp HTTP response details
 */
static void
charity_patch_cb (void *cls,
                  const struct DONAU_PatchCharityResponse *resp)
{
  struct CharityPatchState *ps = cls;

  ps->cph = NULL;
  if (ps->expected_response_code != resp->hr.http_status)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unexpected HTTP response code: %u\n",
                resp->hr.http_status);
    json_dumpf (resp->hr.reply,
                stderr,
                0);
    TALER_TESTING_interpreter_fail (ps->is);
    return;
  }
  TALER_TESTING_interpreter_next (ps->is);
}


/**
 * Run the PATCH command after extracting the referenced charity id.
 *
 * @param cls closure
 * @param cmd command definition
 * @param is interpreter state
 */
static void
charity_patch_run (void *cls,
                   const struct TALER_TESTING_Command *cmd,
                   struct TALER_TESTING_Interpreter *is)
{
  struct CharityPatchState *ps = cls;
  const struct TALER_TESTING_Command *charity_cmd;
  const uint64_t *charity_id;

  (void) cmd;
  ps->is = is;

  charity_cmd =
    TALER_TESTING_interpreter_lookup_command (is,
                                              ps->charity_reference);
  if ( (NULL == charity_cmd) ||
       (GNUNET_OK !=
        TALER_TESTING_get_trait_charity_id (charity_cmd,
                                            &charity_id)) )
  {
    GNUNET_break (0);
    TALER_TESTING_interpreter_fail (is);
    return;
  }
  ps->charity_id = *charity_id;

  ps->cph = DONAU_charity_patch (
    TALER_TESTING_interpreter_get_context (is),
    TALER_TESTING_get_donau_url (is),
    ps->charity_id,
    ps->charity_name,
    ps->charity_url,
    &ps->max_per_year,
    &ps->charity_pub,
    ps->bearer,
    &charity_patch_cb,
    ps);
  if (NULL == ps->cph)
  {
    GNUNET_break (0);
    TALER_TESTING_interpreter_fail (is);
  }
}


/**
 * Cancel any outstanding PATCH request and release resources.
 *
 * @param cls closure
 * @param cmd command being cleaned up
 */
static void
charity_patch_cleanup (void *cls,
                       const struct TALER_TESTING_Command *cmd)
{
  struct CharityPatchState *ps = cls;

  if (NULL != ps->cph)
  {
    TALER_TESTING_command_incomplete (ps->is,
                                      cmd->label);
    DONAU_charity_patch_cancel (ps->cph);
    ps->cph = NULL;
  }
  GNUNET_free (ps);
}


/**
 * Offer traits produced by the PATCH command to subsequent commands.
 *
 * @param cls closure
 * @param[out] ret location to store the requested trait
 * @param trait trait identifier
 * @param index trait index
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
charity_patch_traits (void *cls,
                      const void **ret,
                      const char *trait,
                      unsigned int index)
{
  struct CharityPatchState *ps = cls;
  struct TALER_TESTING_Trait traits[] = {
    TALER_TESTING_make_trait_charity_priv (&ps->charity_priv),
    TALER_TESTING_make_trait_charity_pub (&ps->charity_pub),
    TALER_TESTING_make_trait_charity_id (&ps->charity_id),
    TALER_TESTING_trait_end ()
  };

  return TALER_TESTING_get_trait (traits,
                                  ret,
                                  trait,
                                  index);
}


struct TALER_TESTING_Command
TALER_TESTING_cmd_charity_patch (const char *label,
                                 const char *charity_reference,
                                 const char *name,
                                 const char *url,
                                 const char *max_per_year,
                                 const struct DONAU_BearerToken *bearer,
                                 unsigned int expected_response_code)
{
  struct CharityPatchState *ps;

  ps = GNUNET_new (struct CharityPatchState);
  GNUNET_CRYPTO_eddsa_key_create (&ps->charity_priv.eddsa_priv);
  GNUNET_CRYPTO_eddsa_key_get_public (&ps->charity_priv.eddsa_priv,
                                      &ps->charity_pub.eddsa_pub);
  ps->charity_reference = charity_reference;
  ps->charity_name = name;
  ps->charity_url = url;
  ps->bearer = bearer;
  ps->expected_response_code = expected_response_code;
  if (GNUNET_OK !=
      TALER_string_to_amount (max_per_year,
                              &ps->max_per_year))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to parse amount `%s' for command %s\n",
                max_per_year,
                label);
    GNUNET_free (ps);
    GNUNET_assert (0);
  }

  {
    struct TALER_TESTING_Command cmd = {
      .cls = ps,
      .label = label,
      .run = &charity_patch_run,
      .cleanup = &charity_patch_cleanup,
      .traits = &charity_patch_traits
    };

    return cmd;
  }
}
