/*
  This file is part of TALER
  Copyright (C) 2024, 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-httpd_private-post-donau-instance.c
 * @brief implementation of POST /donau
 * @author Bohdan Potuzhnyi
 * @author Vlada Svirsh
 * @author Christian Grothoff
 */
#include "platform.h"
#include <jansson.h>
#include "donau/donau_service.h"
#include <taler/taler_json_lib.h>
#include <taler/taler_dbevents.h>
#include "taler_merchant_service.h"
#include "taler-merchant-httpd_private-post-donau-instance.h"

/**
 * Context for the POST /donau request handler.
 */
struct PostDonauCtx
{
  /**
   * Stored in a DLL.
   */
  struct PostDonauCtx *next;

  /**
   * Stored in a DLL.
   */
  struct PostDonauCtx *prev;

  /**
   * Connection to the MHD server
   */
  struct MHD_Connection *connection;

  /**
   * Context of the request handler.
   */
  struct TMH_HandlerContext *hc;

  /**
   * URL of the DONAU service
   * to which the charity belongs.
   */
  const char *donau_url;

  /**
   * ID of the charity in the DONAU service.
   */
  uint64_t charity_id;

  /**
   * Handle returned by DONAU_charities_get(); needed to cancel on
   * connection abort, etc.
   */
  struct DONAU_CharityGetHandle *get_handle;

  /**
   * Response to return.
   */
  struct MHD_Response *response;

  /**
   * HTTP status for @e response.
   */
  unsigned int http_status;

  /**
   * #GNUNET_YES if we are suspended,
   * #GNUNET_NO if not,
   * #GNUNET_SYSERR on shutdown
   */
  enum GNUNET_GenericReturnValue suspended;
};


/**
 * Head of active pay context DLL.
 */
static struct PostDonauCtx *pdc_head;

/**
 * Tail of active pay context DLL.
 */
static struct PostDonauCtx *pdc_tail;


void
TMH_force_post_donau_resume ()
{
  for (struct PostDonauCtx *pdc = pdc_head;
       NULL != pdc;
       pdc = pdc->next)
  {
    if (GNUNET_YES == pdc->suspended)
    {
      pdc->suspended = GNUNET_SYSERR;
      MHD_resume_connection (pdc->connection);
    }
  }
}


/**
 * Callback for DONAU_charities_get() to handle the response.
 *
 * @param cls closure with PostDonauCtx
 * @param gcr response from Donau
 */
static void
donau_charity_get_cb (void *cls,
                      const struct DONAU_GetCharityResponse *gcr)
{
  struct PostDonauCtx *pdc = cls;
  enum GNUNET_DB_QueryStatus qs;

  pdc->get_handle = NULL;
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Processing DONAU charity get response");
  /* Anything but 200 => propagate Donau’s response. */
  if (MHD_HTTP_OK != gcr->hr.http_status)
  {
    pdc->http_status = MHD_HTTP_BAD_GATEWAY;
    pdc->response = TALER_MHD_MAKE_JSON_PACK (
      TALER_MHD_PACK_EC (gcr->hr.ec),
      GNUNET_JSON_pack_uint64 ("donau_http_status",
                               gcr->hr.http_status));
    pdc->suspended = GNUNET_NO;
    MHD_resume_connection (pdc->connection);
    TALER_MHD_daemon_trigger ();
    return;
  }

  if (0 !=
      GNUNET_memcmp (&gcr->details.ok.charity.charity_pub.eddsa_pub,
                     &pdc->hc->instance->merchant_pub.eddsa_pub))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Charity key at donau does not match our merchant key\n");
    pdc->http_status = MHD_HTTP_CONFLICT;
    pdc->response = TALER_MHD_make_error (
      TALER_EC_GENERIC_PARAMETER_MALFORMED,
      "charity_pub != merchant_pub");
    MHD_resume_connection (pdc->connection);
    TALER_MHD_daemon_trigger ();
    return;
  }

  qs = TMH_db->insert_donau_instance (TMH_db->cls,
                                      pdc->donau_url,
                                      &gcr->details.ok.charity,
                                      pdc->charity_id);
  switch (qs)
  {
  case GNUNET_DB_STATUS_HARD_ERROR:
    GNUNET_break (0);
    pdc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
    pdc->response = TALER_MHD_make_error (
      TALER_EC_GENERIC_DB_STORE_FAILED,
      "insert_donau_instance");
    break;
  case GNUNET_DB_STATUS_SOFT_ERROR:
    GNUNET_break (0);
    pdc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
    pdc->response = TALER_MHD_make_error (
      TALER_EC_GENERIC_DB_STORE_FAILED,
      "insert_donau_instance");
    break;
  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    /* presumably idempotent + concurrent, no need to notify, but still respond */
    pdc->http_status = MHD_HTTP_NO_CONTENT;
    pdc->response = MHD_create_response_from_buffer_static (0,
                                                            NULL);
    TALER_MHD_add_global_headers (pdc->response,
                                  false);
    break;
  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    {
      struct GNUNET_DB_EventHeaderP es = {
        .size = htons (sizeof (es)),
        .type = htons (TALER_DBEVENT_MERCHANT_DONAU_KEYS)
      };

      TMH_db->event_notify (TMH_db->cls,
                            &es,
                            pdc->donau_url,
                            strlen (pdc->donau_url) + 1);
      pdc->http_status = MHD_HTTP_NO_CONTENT;
      pdc->response = MHD_create_response_from_buffer_static (0,
                                                              NULL);
      TALER_MHD_add_global_headers (pdc->response,
                                    false);
      break;
    }
  }
  pdc->suspended = GNUNET_NO;
  MHD_resume_connection (pdc->connection);
  TALER_MHD_daemon_trigger ();
}


/**
 * Cleanup function for the PostDonauCtx.
 *
 * @param cls closure with PostDonauCtx
 */
static void
post_donau_cleanup (void *cls)
{
  struct PostDonauCtx *pdc = cls;

  if (pdc->get_handle)
  {
    DONAU_charity_get_cancel (pdc->get_handle);
    pdc->get_handle = NULL;
  }
  GNUNET_CONTAINER_DLL_remove (pdc_head,
                               pdc_tail,
                               pdc);
  GNUNET_free (pdc);
}


/**
 * Handle a POST "/donau" request.
 *
 * @param rh context of the handler
 * @param connection the MHD connection to handle
 * @param[in,out] hc context with further information about the request
 * @return MHD result code
 */
MHD_RESULT
TMH_private_post_donau_instance (const struct TMH_RequestHandler *rh,
                                 struct MHD_Connection *connection,
                                 struct TMH_HandlerContext *hc)
{
  struct PostDonauCtx *pdc = hc->ctx;

  if (NULL == pdc)
  {
    enum GNUNET_DB_QueryStatus qs;

    pdc = GNUNET_new (struct PostDonauCtx);
    pdc->connection = connection;
    pdc->hc = hc;
    hc->ctx = pdc;
    hc->cc = &post_donau_cleanup;
    GNUNET_CONTAINER_DLL_insert (pdc_head,
                                 pdc_tail,
                                 pdc);
    {
      struct GNUNET_JSON_Specification spec[] = {
        GNUNET_JSON_spec_string ("donau_url",
                                 &pdc->donau_url),
        GNUNET_JSON_spec_uint64 ("charity_id",
                                 &pdc->charity_id),
        GNUNET_JSON_spec_end ()
      };

      if (GNUNET_OK !=
          TALER_MHD_parse_json_data (connection,
                                     hc->request_body,
                                     spec))
      {
        GNUNET_break_op (0);
        return MHD_NO;
      }
    }
    qs = TMH_db->check_donau_instance (TMH_db->cls,
                                       &hc->instance->merchant_pub,
                                       pdc->donau_url,
                                       pdc->charity_id);
    switch (qs)
    {
    case GNUNET_DB_STATUS_HARD_ERROR:
    case GNUNET_DB_STATUS_SOFT_ERROR:
      GNUNET_break (0);
      pdc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
      return TALER_MHD_reply_with_error (
        connection,
        MHD_HTTP_INTERNAL_SERVER_ERROR,
        TALER_EC_GENERIC_DB_FETCH_FAILED,
        "check_donau_instance");
      break;
    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
      pdc->http_status = MHD_HTTP_NO_CONTENT;
      pdc->response = MHD_create_response_from_buffer_static (0,
                                                              NULL);
      TALER_MHD_add_global_headers (pdc->response,
                                    false);
      goto respond;
    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
      /* normal case, continue below */
      break;
    }

    {
      struct DONAU_CharityPrivateKeyP cp;

      /* Merchant private key IS our charity private key */
      cp.eddsa_priv = hc->instance->merchant_priv.eddsa_priv;
      pdc->get_handle =
        DONAU_charity_get (TMH_curl_ctx,
                           pdc->donau_url,
                           pdc->charity_id,
                           &cp,
                           &donau_charity_get_cb,
                           pdc);
    }
    if (NULL == pdc->get_handle)
    {
      GNUNET_break (0);
      GNUNET_free (pdc);
      return TALER_MHD_reply_with_error (connection,
                                         MHD_HTTP_SERVICE_UNAVAILABLE,
                                         TALER_EC_GENERIC_ALLOCATION_FAILURE,
                                         "Failed to initiate Donau lookup");
    }
    pdc->suspended = GNUNET_YES;
    MHD_suspend_connection (connection);
    return MHD_YES;
  }
respond:
  if (NULL != pdc->response)
  {
    MHD_RESULT res;

    GNUNET_break (GNUNET_NO == pdc->suspended);
    res = MHD_queue_response (pdc->connection,
                              pdc->http_status,
                              pdc->response);
    MHD_destroy_response (pdc->response);
    return res;
  }
  GNUNET_break (GNUNET_SYSERR == pdc->suspended);
  return MHD_NO;
}
