/*
  This file is part of TALER
  (C) 2025 Taler Systems SA

  TALER is free software; you can redistribute it and/or modify it under the
  terms of the GNU Affero 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 taler-merchant-report-generator.c
 * @brief Service for fetching and transmitting merchant reports
 * @author Christian Grothoff
 */
#include "taler/platform.h"
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_db_lib.h>
#include <gnunet/gnunet_curl_lib.h>
#include <taler/taler_merchant_util.h>
#include <taler/taler_curl_lib.h>
#include <taler/taler_dbevents.h>
#include <taler/taler_error_codes.h>
#include "taler/taler_merchantdb_plugin.h"
#include "taler/taler_merchantdb_lib.h"
#include "taler/taler_merchant_service.h"
#include <microhttpd.h>
#include <curl/curl.h>


/**
 * Information about an active reporting activity.
 */
struct ReportActivity
{

  /**
   * Kept in a DLL.
   */
  struct ReportActivity *next;

  /**
   * Kept in a DLL.
   */
  struct ReportActivity *prev;

  /**
   * Transmission program that is running.
   */
  struct GNUNET_Process *proc;

  /**
   * Handle to wait for @e proc to terminate.
   */
  struct GNUNET_ChildWaitHandle *cwh;

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

  /**
   * CURL easy handle for the HTTP request.
   */
  CURL *eh;

  /**
   * Job handle for the HTTP request.
   */
  struct GNUNET_CURL_Job *job;

  /**
   * ID of the instance we are working on.
   */
  char *instance_id;

  /**
   * URL where we request the report from.
   */
  char *url;

  /**
   * Report program section.
   */
  char *report_program_section;

  /**
   * Report description.
   */
  char *report_description;

  /**
   * Target address for transmission.
   */
  char *target_address;

  /**
   * MIME type of the report.
   */
  char *mime_type;

  /**
   * Report we are working on.
   */
  uint64_t report_id;

  /**
   * Next transmission time, already calculated.
   */
  struct GNUNET_TIME_Absolute next_transmission;

  /**
   * HTTP response code.
   */
  long response_code;

  /**
   * Set to true if this is a one-shot report.
   */
  bool one_shot;

};


/**
 * Global return value.
 */
static int global_ret;

/**
 * #GNUNET_YES if we are in test mode and should exit when idle.
 */
static int test_mode;

/**
 * Base URL of the merchant backend.
 */
static char *base_url;

/**
 * Our configuration.
 */
static const struct GNUNET_CONFIGURATION_Handle *cfg;

/**
 * Database plugin.
 */
static struct TALER_MERCHANTDB_Plugin *db_plugin;

/**
 * Event handler for database change notifications.
 */
static struct GNUNET_DB_EventHandler *eh;

/**
 * Task for checking pending reports.
 */
static struct GNUNET_SCHEDULER_Task *report_task;

/**
 * When is the current report_task scheduled to run?
 */
static struct GNUNET_TIME_Absolute report_task_due;

/**
 * Context for CURL operations.
 */
static struct GNUNET_CURL_Context *curl_ctx;

/**
 * Reschedule context for CURL.
 */
static struct GNUNET_CURL_RescheduleContext *curl_rc;

/**
 * Head of DLL of active report activities.
 */
static struct ReportActivity *ra_head;

/**
 * Tail of DLL of active report activities.
 */
static struct ReportActivity *ra_tail;


/**
 * Free a report activity structure.
 *
 * @param[in] ra report activity to free
 */
static void
free_ra (struct ReportActivity *ra)
{
  if (NULL != ra->cwh)
  {
    GNUNET_wait_child_cancel (ra->cwh);
    ra->cwh = NULL;
  }
  if (NULL != ra->proc)
  {
    GNUNET_break (GNUNET_OK ==
                  GNUNET_process_kill (ra->proc,
                                       SIGKILL));
    GNUNET_break (GNUNET_OK ==
                  GNUNET_process_wait (ra->proc,
                                       true,
                                       NULL,
                                       NULL));
    GNUNET_process_destroy (ra->proc);
    ra->proc = NULL;
  }
  TALER_curl_easy_post_finished (&ra->post_ctx);
  if (NULL != ra->eh)
  {
    curl_easy_cleanup (ra->eh);
    ra->eh = NULL;
  }
  if (NULL != ra->job)
  {
    GNUNET_CURL_job_cancel (ra->job);
    ra->job = NULL;
  }
  GNUNET_CONTAINER_DLL_remove (ra_head,
                               ra_tail,
                               ra);
  GNUNET_free (ra->instance_id);
  GNUNET_free (ra->report_program_section);
  GNUNET_free (ra->report_description);
  GNUNET_free (ra->target_address);
  GNUNET_free (ra->mime_type);
  GNUNET_free (ra->url);
  GNUNET_free (ra);
}


/**
 * Check for pending reports and process them.
 *
 * @param cls closure (unused)
 */
static void
check_pending_reports (void *cls);


/**
 * Finish transmission of a report and update database.
 *
 * @param[in] ra report activity to finish
 * @param ec error code (#TALER_EC_NONE on success)
 * @param error_details human-readable error details (NULL on success)
 */
static void
finish_transmission (struct ReportActivity *ra,
                     enum TALER_ErrorCode ec,
                     const char *error_details)
{
  enum GNUNET_DB_QueryStatus qs;
  struct GNUNET_TIME_Timestamp next_ts;

  next_ts = GNUNET_TIME_absolute_to_timestamp (ra->next_transmission);
  if ( (TALER_EC_NONE == ec) &&
       (ra->one_shot) )
  {
    qs = db_plugin->delete_report (db_plugin->cls,
                                   ra->instance_id,
                                   ra->report_id);
  }
  else
  {
    qs = db_plugin->update_report_status (db_plugin->cls,
                                          ra->instance_id,
                                          ra->report_id,
                                          next_ts,
                                          ec,
                                          error_details);
  }
  if (qs < 0)
  {
    free_ra (ra);
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to update report status: %d\n",
                qs);
    global_ret = EXIT_FAILURE;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  if ( (NULL == report_task) ||
       (GNUNET_TIME_absolute_cmp (report_task_due,
                                  >,
                                  ra->next_transmission)) )
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Scheduling next report for %s\n",
                GNUNET_TIME_absolute2s (ra->next_transmission));
    if (NULL != report_task)
      GNUNET_SCHEDULER_cancel (report_task);
    report_task_due = ra->next_transmission;
    report_task = GNUNET_SCHEDULER_add_at (ra->next_transmission,
                                           &check_pending_reports,
                                           NULL);
  }
  free_ra (ra);
  if (test_mode &&
      GNUNET_TIME_absolute_is_future (report_task_due) &&
      (NULL == ra_head))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Test mode, exiting because of going idle\n");
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
}


/**
 * Callback invoked when the child process terminates.
 *
 * @param cls closure, a `struct ReportActivity *`
 * @param type type of the process
 * @param exit_code exit code of the process
 */
static void
child_completed_cb (void *cls,
                    enum GNUNET_OS_ProcessStatusType type,
                    long unsigned int exit_code)
{
  struct ReportActivity *ra = cls;
  enum TALER_ErrorCode ec;
  char *error_details = NULL;

  ra->cwh = NULL;
  GNUNET_process_destroy (ra->proc);
  ra->proc = NULL;
  if ( (GNUNET_OS_PROCESS_EXITED != type) ||
       (0 != exit_code) )
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Report transmission program failed with status %d/%lu\n",
                (int) type,
                exit_code);
    ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    GNUNET_asprintf (&error_details,
                     "Report transmission program exited with status %d/%lu",
                     (int) type,
                     exit_code);
  }
  else
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Report transmitted successfully\n");
    ec = TALER_EC_NONE;
  }
  finish_transmission (ra,
                       ec,
                       error_details);
  GNUNET_free (error_details);
}


/**
 * Transmit a report using the respective report program.
 *
 * @param[in,out] ra which report activity are we working on
 * @param report_len length of @a report
 * @param report binary report data to transmit
 */
static void
transmit_report (struct ReportActivity *ra,
                 size_t report_len,
                 const void *report)
{
  const char *binary;
  struct GNUNET_DISK_FileHandle *stdin_handle;

  {
    char *section;

    GNUNET_asprintf (&section,
                     "report-generator-%s",
                     ra->report_program_section);
    if (GNUNET_OK !=
        GNUNET_CONFIGURATION_get_value_string (cfg,
                                               section,
                                               "BINARY",
                                               (char **) &binary))
    {
      GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                                 section,
                                 "BINARY");
      finish_transmission (ra,
                           TALER_EC_MERCHANT_GENERIC_REPORT_GENERATOR_UNCONFIGURED,
                           section);
      GNUNET_free (section);
      return;
    }
    GNUNET_free (section);
  }

  {
    struct GNUNET_DISK_PipeHandle *stdin_pipe;

    stdin_pipe = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
    if (NULL == stdin_pipe)
    {
      GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
                           "pipe");
      finish_transmission (ra,
                           TALER_EC_GENERIC_OS_RESOURCE_ALLOCATION_FAILURE,
                           "pipe");
      return;
    }

    ra->proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
    GNUNET_assert (GNUNET_OK ==
                   GNUNET_process_set_options (
                     ra->proc,
                     GNUNET_process_option_inherit_rpipe (stdin_pipe,
                                                          STDIN_FILENO)));
    if (GNUNET_OK !=
        GNUNET_process_run_command_va (ra->proc,
                                       binary,
                                       binary,
                                       "-d",
                                       ra->report_description,
                                       "-m",
                                       ra->mime_type,
                                       "-t",
                                       ra->target_address,
                                       NULL))
    {
      GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
                                "exec",
                                binary);
      GNUNET_process_destroy (ra->proc);
      ra->proc = NULL;
      GNUNET_DISK_pipe_close (stdin_pipe);
      finish_transmission (ra,
                           TALER_EC_MERCHANT_REPORT_GENERATOR_FAILED,
                           "Could not execute report generator binary");
      return;
    }

    /* Write report data to stdin of child process */
    stdin_handle = GNUNET_DISK_pipe_detach_end (stdin_pipe,
                                                GNUNET_DISK_PIPE_END_WRITE);
    GNUNET_DISK_pipe_close (stdin_pipe);
  }

  {
    size_t off = 0;

    while (off < report_len)
    {
      ssize_t wrote;

      wrote = GNUNET_DISK_file_write (stdin_handle,
                                      report,
                                      report_len);
      if (wrote <= 0)
        break;
      off += (size_t) wrote;
    }
    GNUNET_DISK_file_close (stdin_handle);

    if (off != report_len)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Failed to write report data to child process stdin\n");
      finish_transmission (ra,
                           TALER_EC_MERCHANT_REPORT_GENERATOR_FAILED,
                           "Failed to write to transmission program");
      return;
    }
  }

  /* Wait for child to complete */
  ra->cwh = GNUNET_wait_child (ra->proc,
                               &child_completed_cb,
                               ra);
}


/**
 * Callback invoked when CURL request completes.
 *
 * @param cls closure, a `struct ReportActivity *`
 * @param response_code HTTP response code
 * @param body http body of the response
 * @param body_size number of bytes in @a body
 */
static void
curl_completed_cb (void *cls,
                   long response_code,
                   const void *body,
                   size_t body_size)
{
  struct ReportActivity *ra = cls;

  ra->job = NULL;
  ra->response_code = response_code;
  if (MHD_HTTP_OK != response_code)
  {
    char *error_details;

    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to fetch report data: HTTP %ld\n",
                response_code);
    GNUNET_asprintf (&error_details,
                     "HTTP request failed with status %ld from `%s'",
                     response_code,
                     ra->url);
    finish_transmission (ra,
                         TALER_EC_MERCHANT_REPORT_FETCH_FAILED,
                         error_details);
    GNUNET_free (error_details);
    return;
  }
  transmit_report (ra,
                   body_size,
                   body);
}


/**
 * Function to fetch data from @a data_source at @a instance_id
 * and to send it to the @a target_address
 *
 * @param[in,out] ra which report activity are we working on
 * @param mime_type mime type to request from @a data_source
 * @param report_token token to get access to the report
 */
static void
fetch_and_transmit (
  struct ReportActivity *ra,
  const char *mime_type,
  const struct TALER_MERCHANT_ReportToken *report_token)
{
  GNUNET_asprintf (&ra->url,
                   "%sreports/%llu",
                   base_url,
                   (unsigned long long) ra->report_id);
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Fetching report from %s\n",
              ra->url);
  ra->eh = curl_easy_init ();
  if (NULL == ra->eh)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to initialize CURL handle\n");
    finish_transmission (ra,
                         TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE,
                         "curl_easy_init");
    return;
  }

  {
    char *accept_header;

    GNUNET_asprintf (&accept_header,
                     "Accept: %s",
                     mime_type);
    ra->post_ctx.headers = curl_slist_append (ra->post_ctx.headers,
                                              accept_header);
    GNUNET_free (accept_header);
  }
  GNUNET_assert (CURLE_OK ==
                 curl_easy_setopt (ra->eh,
                                   CURLOPT_URL,
                                   ra->url));
  {
    json_t *req;

    req = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_data_auto ("report_token",
                                  report_token));
    if (GNUNET_OK !=
        TALER_curl_easy_post (&ra->post_ctx,
                              ra->eh,
                              req))
    {
      GNUNET_break (0);
      json_decref (req);
      finish_transmission (ra,
                           TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE,
                           "TALER_curl_easy_post");
      return;
    }
    json_decref (req);
  }
  ra->job = GNUNET_CURL_job_add_raw (curl_ctx,
                                     ra->eh,
                                     ra->post_ctx.headers,
                                     &curl_completed_cb,
                                     ra);
  ra->eh = NULL;
}


/**
 * Callback invoked for each pending report.
 *
 * @param cls closure
 * @param instance_id name of the instance
 * @param report_id serial number of the report
 * @param report_program_section configuration section of program
 *   for report generation
 * @param report_description text describing the report
 * @param mime_type mime type to request
 * @param report_token token to authorize access to the data source
 * @param target_address where to send report data
 * @param frequency report frequency
 * @param frequency_shift how much to shift the report time from a
 *   multiple of the report @a frequency
 * @param next_transmission when is the next transmission of this report
 *   due
 * @param one_shot true if the report should be removed from the
 *   list after generation instead of being repeated
 */
static void
process_pending_report (
  void *cls,
  const char *instance_id,
  uint64_t report_id,
  const char *report_program_section,
  const char *report_description,
  const char *mime_type,
  const struct TALER_MERCHANT_ReportToken *report_token,
  const char *target_address,
  struct GNUNET_TIME_Relative frequency,
  struct GNUNET_TIME_Relative frequency_shift,
  struct GNUNET_TIME_Absolute next_transmission,
  bool one_shot)
{
  struct GNUNET_TIME_Absolute *next = cls;
  struct ReportActivity *ra;

  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Next report %llu is pending at %s\n",
              (unsigned long long) report_id,
              GNUNET_TIME_absolute2s (next_transmission));
  *next = next_transmission;
  if (GNUNET_TIME_absolute_is_future (next_transmission))
    return;
  *next = GNUNET_TIME_UNIT_ZERO_ABS; /* there might be more! */
  if ( (one_shot) ||
       (GNUNET_TIME_relative_is_zero (frequency)) )
  {
    next_transmission = GNUNET_TIME_UNIT_FOREVER_ABS;
  }
  else
  {
    next_transmission =
      GNUNET_TIME_absolute_add (
        GNUNET_TIME_absolute_round_down (GNUNET_TIME_absolute_get (),
                                         frequency),
        GNUNET_TIME_relative_add (frequency,
                                  frequency_shift));
  }
  if (! GNUNET_TIME_absolute_is_future (next_transmission))
  {
    /* frequency near-zero!? */
    GNUNET_break (0);
    next_transmission = GNUNET_TIME_relative_to_absolute (
      GNUNET_TIME_UNIT_MINUTES);
  }
  ra = GNUNET_new (struct ReportActivity);
  ra->instance_id = GNUNET_strdup (instance_id);
  ra->report_id = report_id;
  ra->next_transmission = next_transmission;
  ra->report_program_section = GNUNET_strdup (report_program_section);
  ra->report_description = GNUNET_strdup (report_description);
  ra->target_address = GNUNET_strdup (target_address);
  ra->mime_type = GNUNET_strdup (mime_type);
  ra->one_shot = one_shot;
  GNUNET_CONTAINER_DLL_insert (ra_head,
                               ra_tail,
                               ra);
  fetch_and_transmit (ra,
                      mime_type,
                      report_token);
}


static void
check_pending_reports (void *cls)
{
  enum GNUNET_DB_QueryStatus qs;
  struct GNUNET_TIME_Absolute next;

  (void) cls;
  report_task = NULL;
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Checking for pending reports...\n");
  next = GNUNET_TIME_UNIT_FOREVER_ABS;
  qs = db_plugin->lookup_reports_pending (db_plugin->cls,
                                          &process_pending_report,
                                          &next);
  if (qs < 0)
  {
    GNUNET_break (0);
    global_ret = EXIT_FAILURE;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  if (NULL != ra_head)
    return; /* wait for completion */
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Found %d reports pending, next at %s\n",
              (int) qs,
              GNUNET_TIME_absolute2s (next));
  GNUNET_assert (NULL == report_task);
  if (test_mode &&
      GNUNET_TIME_absolute_is_future (next) &&
      (NULL == ra_head))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Test mode, existing because of going idle\n");
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  report_task_due = next;
  report_task = GNUNET_SCHEDULER_add_at (next,
                                         &check_pending_reports,
                                         NULL);
}


/**
 * Callback invoked when a MERCHANT_REPORT_UPDATE event is received.
 *
 * @param cls closure (unused)
 * @param extra additional event data (unused)
 * @param extra_size size of @a extra
 */
static void
report_update_cb (void *cls,
                  const void *extra,
                  size_t extra_size)
{
  (void) cls;
  (void) extra;
  (void) extra_size;

  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Received MERCHANT_REPORT_UPDATE event\n");
  /* Cancel any pending check and schedule immediate execution */
  if (NULL != report_task)
    GNUNET_SCHEDULER_cancel (report_task);
  report_task_due = GNUNET_TIME_UNIT_ZERO_ABS;
  report_task = GNUNET_SCHEDULER_add_now (&check_pending_reports,
                                          NULL);
}


/**
 * Shutdown the service cleanly.
 *
 * @param cls closure (unused)
 */
static void
do_shutdown (void *cls)
{
  struct ReportActivity *ra;

  (void) cls;

  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Shutting down report generator service\n");

  while (NULL != (ra = ra_head))
    free_ra (ra);

  if (NULL != report_task)
  {
    GNUNET_SCHEDULER_cancel (report_task);
    report_task = NULL;
  }
  if (NULL != curl_rc)
  {
    GNUNET_CURL_gnunet_rc_destroy (curl_rc);
    curl_rc = NULL;
  }
  if (NULL != curl_ctx)
  {
    GNUNET_CURL_fini (curl_ctx);
    curl_ctx = NULL;
  }
  if (NULL != eh)
  {
    db_plugin->event_listen_cancel (eh);
    eh = NULL;
  }
  if (NULL != db_plugin)
  {
    TALER_MERCHANTDB_plugin_unload (db_plugin);
    db_plugin = NULL;
  }
  GNUNET_free (base_url);
  base_url = NULL;
}


/**
 * Main function for the report generator service.
 *
 * @param cls closure
 * @param args remaining command-line arguments
 * @param cfgfile name of the configuration file used
 * @param config configuration
 */
static void
run (void *cls,
     char *const *args,
     const char *cfgfile,
     const struct GNUNET_CONFIGURATION_Handle *config)
{
  (void) cls;
  (void) args;
  (void) cfgfile;

  cfg = config;
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (cfg,
                                             "merchant",
                                             "BASE_URL",
                                             &base_url))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "merchant",
                               "BASE_URL");
    global_ret = EXIT_NOTCONFIGURED;
    return;
  }
  if (! TALER_is_web_url (base_url))
  {
    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                               "merchant",
                               "BASE_URL",
                               "Not a Web URL");
    global_ret = EXIT_NOTCONFIGURED;
    return;
  }

  /* Ensure base_url ends with '/' */
  if ('/' != base_url[strlen (base_url) - 1])
  {
    char *tmp;

    GNUNET_asprintf (&tmp,
                     "%s/",
                     base_url);
    GNUNET_free (base_url);
    base_url = tmp;
  }

  GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
                                 NULL);

  curl_ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
                               &curl_rc);
  if (NULL == curl_ctx)
  {
    GNUNET_break (0);
    global_ret = EXIT_FAILURE;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  curl_rc = GNUNET_CURL_gnunet_rc_create (curl_ctx);

  db_plugin = TALER_MERCHANTDB_plugin_load (cfg);
  if (NULL == db_plugin)
  {
    GNUNET_break (0);
    global_ret = EXIT_NOTINSTALLED;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  if (GNUNET_OK !=
      db_plugin->connect (db_plugin->cls))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to connect to database. Consider running taler-merchant-dbinit!\n");
    GNUNET_SCHEDULER_shutdown ();
    global_ret = EXIT_FAILURE;
    return;
  }

  {
    struct GNUNET_DB_EventHeaderP ev = {
      .size = htons (sizeof (ev)),
      .type = htons (TALER_DBEVENT_MERCHANT_REPORT_UPDATE)
    };

    eh = db_plugin->event_listen (db_plugin->cls,
                                  &ev,
                                  GNUNET_TIME_UNIT_FOREVER_REL,
                                  &report_update_cb,
                                  NULL);
    if (NULL == eh)
    {
      GNUNET_break (0);
      global_ret = EXIT_FAILURE;
      GNUNET_SCHEDULER_shutdown ();
      return;
    }
  }
  report_task = GNUNET_SCHEDULER_add_now (&check_pending_reports,
                                          NULL);
}


/**
 * The main function of the report generator service.
 *
 * @param argc number of arguments from the command line
 * @param argv command line arguments
 * @return 0 ok, 1 on error
 */
int
main (int argc,
      char *const *argv)
{
  struct GNUNET_GETOPT_CommandLineOption options[] = {
    GNUNET_GETOPT_option_flag ('t',
                               "test",
                               "run in test mode and exit when idle",
                               &test_mode),
    GNUNET_GETOPT_option_timetravel ('T',
                                     "timetravel"),
    GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
    GNUNET_GETOPT_OPTION_END
  };
  enum GNUNET_GenericReturnValue ret;

  ret = GNUNET_PROGRAM_run (
    TALER_MERCHANT_project_data (),
    argc, argv,
    "taler-merchant-report-generator",
    "Fetch and transmit periodic merchant reports",
    options,
    &run,
    NULL);
  if (GNUNET_SYSERR == ret)
    return EXIT_INVALIDARGUMENT;
  if (GNUNET_NO == ret)
    return EXIT_SUCCESS;
  return global_ret;
}


/* end of taler-merchant-report-generator.c */
