/*
  This file is part of ANASTASIS
  Copyright (C) 2022 Anastasis SARL

  ANASTASIS 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 2.1,
  or (at your option) any later version.

  ANASTASIS 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 ANASTASIS; see the file COPYING.LGPL.  If not,
  see <http://www.gnu.org/licenses/>
*/

/**
 * @file restclient/anastasis_api_policy_meta_lookup.c
 * @brief Implementation of the /policy/$POL/meta GET request
 * @author Christian Grothoff
 */
#include "platform.h"
#include <curl/curl.h>
#include <jansson.h>
#include <microhttpd.h> /* just for HTTP status codes */
#include "anastasis_service.h"
#include "anastasis_api_curl_defaults.h"
#include <gnunet/gnunet_json_lib.h>
#include <taler/taler_signatures.h>


/**
 * @brief A Meta Operation Handle
 */
struct ANASTASIS_PolicyMetaLookupOperation
{

  /**
   * The url for this request, including parameters.
   */
  char *url;

  /**
   * Handle for the request.
   */
  struct GNUNET_CURL_Job *job;

  /**
   * Function to call with the result.
   */
  ANASTASIS_PolicyMetaLookupCallback cb;

  /**
   * Closure for @a cb.
   */
  void *cb_cls;

  /**
   * Reference to the execution context.
   */
  struct GNUNET_CURL_Context *ctx;

  /**
   * Public key of the account we are downloading from.
   */
  struct ANASTASIS_CRYPTO_AccountPublicKeyP account_pub;

  /**
   * Maximum version to fetch.
   */
  uint32_t max_version;

};


/**
 * Process GET /policy/$POL/meta response
 *
 * @param cls our `struct ANASTASIS_PolicyMetaLookupOperation *`
 * @param response_code HTTP status
 * @param data response body, a `json_t *`, NULL on error
 */
static void
handle_policy_meta_lookup_finished (void *cls,
                                    long response_code,
                                    const void *response)
{
  struct ANASTASIS_PolicyMetaLookupOperation *plo = cls;
  const json_t *json = response;
  struct ANASTASIS_MetaDownloadDetails mdd = {
    .http_status = response_code,
    .response = json
  };

  plo->job = NULL;
  switch (response_code)
  {
  case 0:
    /* Hard error */
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Backend didn't even return from GET /policy\n");
    break;
  case MHD_HTTP_OK:
    {
      size_t mlen = json_object_size (json);

      /* put a cap, as we will stack-allocate below and the
         current service LIMITs the result to 1000 anyway;
         could theoretically be increased in the future, but
         then we should not put this onto the stack anymore... */
      if (mlen > 10000)
      {
        GNUNET_break (0);
        response_code = 0;
        break;
      }
      {
        struct ANASTASIS_MetaDataEntry metas[GNUNET_NZL (mlen)];
        void *md[GNUNET_NZL (mlen)];
        size_t off = 0;
        const char *label;
        const json_t *val;

        memset (md,
                0,
                sizeof (md));
        mdd.details.ok.metas = metas;
        mdd.details.ok.metas_length = mlen;
        json_object_foreach ((json_t *) json,
                             label,
                             val)
        {
          unsigned int ver;
          char dummy;
          struct GNUNET_JSON_Specification spec[] = {
            GNUNET_JSON_spec_varsize ("meta",
                                      &md[off],
                                      &metas[off].meta_data_size),
            GNUNET_JSON_spec_timestamp ("upload_time",
                                        &metas[off].server_time),
            GNUNET_JSON_spec_end ()
          };

          if (1 != sscanf (label,
                           "%u%c",
                           &ver,
                           &dummy))
          {
            GNUNET_break (0);
            mdd.http_status = 0;
            mdd.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
            break;
          }
          if (GNUNET_OK !=
              GNUNET_JSON_parse (val,
                                 spec,
                                 NULL, NULL))
          {
            GNUNET_break_op (0);
            mdd.http_status = 0;
            mdd.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
            break;
          }
          metas[off].version = (uint32_t) ver;
          metas[off].meta_data = md[off];
          off++;
        }
        if (off < mlen)
        {
          GNUNET_break (0);
          mdd.http_status = 0;
          mdd.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
          for (size_t i = 0; i<off; i++)
            GNUNET_free (md[i]);
          break;
        }
        plo->cb (plo->cb_cls,
                 &mdd);
        for (size_t i = 0; i<off; i++)
          GNUNET_free (md[i]);
        plo->cb = NULL;
      }
      ANASTASIS_policy_meta_lookup_cancel (plo);
      return;
    }
  case MHD_HTTP_BAD_REQUEST:
    /* This should never happen, either us or the anastasis server is buggy
       (or API version conflict); just pass JSON reply to the application */
    break;
  case MHD_HTTP_NOT_FOUND:
    /* Nothing really to verify */
    break;
  case MHD_HTTP_INTERNAL_SERVER_ERROR:
    /* Server had an internal issue; we should retry, but this API
       leaves this to the application */
    break;
  default:
    /* unexpected response code */
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unexpected response code %u\n",
                (unsigned int) response_code);
    GNUNET_break (0);
    break;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "HTTP status for policy meta lookup is %u\n",
              (unsigned int) response_code);
  plo->cb (plo->cb_cls,
           &mdd);
  plo->cb = NULL;
  ANASTASIS_policy_meta_lookup_cancel (plo);
}


struct ANASTASIS_PolicyMetaLookupOperation *
ANASTASIS_policy_meta_lookup (
  struct GNUNET_CURL_Context *ctx,
  const char *backend_url,
  const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
  uint32_t max_version,
  ANASTASIS_PolicyMetaLookupCallback cb,
  void *cb_cls)
{
  struct ANASTASIS_PolicyMetaLookupOperation *plo;
  CURL *eh;
  char *path;
  char max_version_s[14];

  GNUNET_assert (NULL != cb);
  plo = GNUNET_new (struct ANASTASIS_PolicyMetaLookupOperation);
  plo->account_pub = *anastasis_pub;
  {
    char *acc_pub_str;

    acc_pub_str = GNUNET_STRINGS_data_to_string_alloc (anastasis_pub,
                                                       sizeof (*anastasis_pub));
    GNUNET_asprintf (&path,
                     "policy/%s/meta",
                     acc_pub_str);
    GNUNET_free (acc_pub_str);
  }
  GNUNET_snprintf (max_version_s,
                   sizeof (max_version_s),
                   "%u",
                   (unsigned int) max_version);
  plo->url = TALER_url_join (backend_url,
                             path,
                             "max_version",
                             max_version_s,
                             NULL);
  GNUNET_free (path);
  eh = ANASTASIS_curl_easy_get_ (plo->url);
  GNUNET_assert (NULL != eh);
  plo->cb = cb;
  plo->cb_cls = cb_cls;
  plo->job = GNUNET_CURL_job_add (ctx,
                                  eh,
                                  &handle_policy_meta_lookup_finished,
                                  plo);
  return plo;
}


void
ANASTASIS_policy_meta_lookup_cancel (
  struct ANASTASIS_PolicyMetaLookupOperation *plo)
{
  if (NULL != plo->job)
  {
    GNUNET_CURL_job_cancel (plo->job);
    plo->job = NULL;
  }
  GNUNET_free (plo->url);
  GNUNET_free (plo);
}
