/*
   This file is part of TALER
   Copyright (C) 2024 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 plugin_donaudb_postgres.c
 * @brief Low-level (statement-level) Postgres database access for the donau
 * @author Johannes Casaburi
 */
#include "donau_config.h"
#include <poll.h>
#include <pthread.h>
#include <libpq-fe.h>
#include <taler/taler_error_codes.h>
#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include <taler/taler_util.h>
#include <taler/taler_json_lib.h>
#include "donaudb_plugin.h"
#include "pg_helper.h"
#include "pg_preflight.h"
#include "pg_commit.h"
#include "pg_drop_tables.h"
#include "pg_start.h"
#include "pg_rollback.h"
#include "pg_create_tables.h"
#include "pg_event_listen.h"
#include "pg_event_listen_cancel.h"
#include "pg_event_notify.h"
#include "pg_insert_donation_unit.h"
#include "pg_iterate_donation_units.h"
#include "pg_insert_history_entry.h"
#include "pg_get_history.h"
#include "pg_insert_issued_receipt.h"
#include "pg_insert_submitted_receipts.h"
#include "pg_iterate_submitted_receipts.h"
#include "pg_insert_signing_key.h"
#include "pg_iterate_active_signing_keys.h"
#include "pg_lookup_signing_key.h"
#include "pg_lookup_charity.h"
#include "pg_lookup_donation_unit_amount.h"
#include "pg_lookup_issued_receipts.h"
#include "pg_get_charities.h"
#include "pg_insert_charity.h"
#include "pg_update_charity.h"
#include "pg_do_charity_delete.h"

/**
 * Set to 1 to enable Postgres auto_explain module. This will
 * slow down things a _lot_, but also provide extensive logging
 * in the Postgres database logger for performance analysis.
 */
#define AUTO_EXPLAIN 0


/**
 * Log a really unexpected PQ error with all the details we can get hold of.
 *
 * @param result PQ result object of the PQ operation that failed
 * @param conn SQL connection that was used
 */
#define BREAK_DB_ERR(result,conn) do {                                  \
    GNUNET_break (0);                                                   \
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,                                \
                "Database failure: %s/%s/%s/%s/%s",                     \
                PQresultErrorField (result, PG_DIAG_MESSAGE_PRIMARY),   \
                PQresultErrorField (result, PG_DIAG_MESSAGE_DETAIL),    \
                PQresultErrorMessage (result),                          \
                PQresStatus (PQresultStatus (result)),                  \
                PQerrorMessage (conn));                                 \
} while (0)


/**
 * Connect to the database if the connection does not exist yet.
 *
 * @param pg the plugin-specific state
 * @return #GNUNET_OK on success
 */
enum GNUNET_GenericReturnValue
DH_PG_internal_setup (struct PostgresClosure *pg)
{
  if (NULL == pg->conn)
  {
#if AUTO_EXPLAIN
    /* Enable verbose logging to see where queries do not
       properly use indices */
    struct GNUNET_PQ_ExecuteStatement es[] = {
      GNUNET_PQ_make_try_execute ("LOAD 'auto_explain';"),
      GNUNET_PQ_make_try_execute ("SET auto_explain.log_min_duration=50;"),
      GNUNET_PQ_make_try_execute ("SET auto_explain.log_timing=TRUE;"),
      GNUNET_PQ_make_try_execute ("SET auto_explain.log_analyze=TRUE;"),
      /* https://wiki.postgresql.org/wiki/Serializable suggests to really
         force the default to 'serializable' if SSI is to be used. */
      GNUNET_PQ_make_try_execute (
        "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"),
      GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"),
      GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"),
      GNUNET_PQ_make_try_execute ("SET search_path TO donau;"),
      GNUNET_PQ_EXECUTE_STATEMENT_END
    };
#else
    struct GNUNET_PQ_ExecuteStatement es[] = {
      GNUNET_PQ_make_try_execute (
        "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"),
      GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"),
      GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"),
      GNUNET_PQ_make_try_execute ("SET autocommit=OFF;"),
      GNUNET_PQ_make_try_execute ("SET search_path TO donau;"),
      GNUNET_PQ_EXECUTE_STATEMENT_END
    };
#endif
    struct GNUNET_PQ_Context *db_conn;

    db_conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
                                          "donaudb-postgres",
                                          NULL,
                                          es,
                                          NULL);
    if (NULL == db_conn)
      return GNUNET_SYSERR;

    pg->prep_gen++;
    pg->conn = db_conn;
  }
  if (NULL == pg->transaction_name)
    GNUNET_PQ_reconnect_if_down (pg->conn);
  return GNUNET_OK;
}


/**
 * Initialize Postgres database subsystem.
 *
 * @param cls a configuration instance
 * @return NULL on error, otherwise a `struct
 *         DONAUDB_Plugin`
 */
void *
libdonau_plugin_donaudb_postgres_init (void *cls);

void *
libdonau_plugin_donaudb_postgres_init (void *cls)
{
  const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
  struct PostgresClosure *pg;
  struct DONAUDB_Plugin *plugin;

  pg = GNUNET_new (struct PostgresClosure);
  pg->cfg = cfg;
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (cfg,
                                             "donau",
                                             "BASE_URL",
                                             &pg->donau_url))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "donau",
                               "BASE_URL");
    GNUNET_free (pg);
    return NULL;
  }
  if (GNUNET_OK !=
      TALER_config_get_currency (cfg,
                                 "donau",
                                 &pg->currency))
  {
    GNUNET_free (pg->donau_url);
    GNUNET_free (pg);
    return NULL;
  }
  if (GNUNET_OK !=
      DH_PG_internal_setup (pg))
  {
    GNUNET_free (pg->donau_url);
    GNUNET_free (pg->currency);
    GNUNET_free (pg);
    return NULL;
  }
  plugin = GNUNET_new (struct DONAUDB_Plugin);
  plugin->cls = pg;
  plugin->drop_tables
    = &DH_PG_drop_tables;
  plugin->commit
    = &DH_PG_commit;
  plugin->preflight
    = &DH_PG_preflight;
  plugin->start
    = &DH_PG_start;
  plugin->rollback
    = &DH_PG_rollback;
  plugin->create_tables
    = &DH_PG_create_tables;
  plugin->event_listen
    = &DH_PG_event_listen;
  plugin->event_listen_cancel
    = &DH_PG_event_listen_cancel;
  plugin->event_notify
    = &DH_PG_event_notify;
  plugin->insert_donation_unit
    = &DH_PG_insert_donation_unit;
  plugin->iterate_donation_units
    = &DH_PG_iterate_donation_units;
  plugin->lookup_donation_unit_amount
    = &DH_PG_lookup_donation_unit_amount;
  plugin->insert_history_entry
    = &DH_PG_insert_history_entry;
  plugin->get_history
    = &DH_PG_get_history;
  plugin->insert_issued_receipt
    = &DH_PG_insert_issued_receipt;
  plugin->lookup_issued_receipts
    = &DH_PG_lookup_issued_receipts;
  plugin->insert_submitted_receipts
    = &DH_PG_insert_submitted_receipts;
  plugin->insert_signing_key
    = &DH_PG_insert_signing_key;
  plugin->lookup_signing_key
    = &DH_PG_lookup_signing_key;
  plugin->iterate_active_signing_keys
    = &DH_PG_iterate_active_signing_keys;
  plugin->iterate_submitted_receipts
    = &DH_PG_iterate_submitted_receipts;
  plugin->lookup_charity
    = &DH_PG_lookup_charity;
  plugin->insert_charity
    = &DH_PG_insert_charity;
  plugin->update_charity
    = &DH_PG_update_charity;
  plugin->get_charities
    = &DH_PG_get_charities;
  plugin->do_charity_delete
    = &DH_PG_do_charity_delete;

  return plugin;
}


/**
 * Shutdown Postgres database subsystem.
 *
 * @param cls a `struct DONAUDB_Plugin`
 * @return NULL (always)
 */
void *
libdonau_plugin_donaudb_postgres_done (void *cls);

void *
libdonau_plugin_donaudb_postgres_done (void *cls)
{
  struct DONAUDB_Plugin *plugin = cls;
  struct PostgresClosure *pg = plugin->cls;

  if (NULL != pg->conn)
  {
    GNUNET_PQ_disconnect (pg->conn);
    pg->conn = NULL;
  }
  GNUNET_free (pg->donau_url);
  GNUNET_free (pg->currency);
  GNUNET_free (pg);
  GNUNET_free (plugin);
  return NULL;
}


/* end of plugin_donaudb_postgres.c */
