#include "config.h"
  #include <lightningd/log.h>

static void db_log_(struct log *log UNUSED, enum log_level level UNUSED, const struct node_id *node_id UNUSED, bool call_notifier UNUSED, const char *fmt UNUSED, ...)
{
}
#define log_ db_log_

#include "db/bindings.c"
#include "db/db_sqlite3.c"
#include "db/exec.c"
#include "db/utils.c"
#include "wallet/db.c"

#include "test_utils.h"

#include <common/setup.h>
#include <common/utils.h>
#include <stdio.h>
#include <unistd.h>

/* AUTOGENERATED MOCKS START */
/* Generated stub for bip32_pubkey */
void bip32_pubkey(struct lightningd *ld UNNEEDED, struct pubkey *pubkey UNNEEDED, u32 index UNNEEDED)
{ fprintf(stderr, "bip32_pubkey called!\n"); abort(); }
/* Generated stub for derive_channel_id */
void derive_channel_id(struct channel_id *channel_id UNNEEDED,
		       const struct bitcoin_outpoint *outpoint UNNEEDED)
{ fprintf(stderr, "derive_channel_id called!\n"); abort(); }
/* Generated stub for fatal */
void   fatal(const char *fmt UNNEEDED, ...)
{ fprintf(stderr, "fatal called!\n"); abort(); }
/* Generated stub for fatal_vfmt */
void  fatal_vfmt(const char *fmt UNNEEDED, va_list ap UNNEEDED)
{ fprintf(stderr, "fatal_vfmt called!\n"); abort(); }
/* Generated stub for fromwire_hsmd_get_channel_basepoints_reply */
bool fromwire_hsmd_get_channel_basepoints_reply(const void *p UNNEEDED, struct basepoints *basepoints UNNEEDED, struct pubkey *funding_pubkey UNNEEDED)
{ fprintf(stderr, "fromwire_hsmd_get_channel_basepoints_reply called!\n"); abort(); }
/* Generated stub for fromwire_hsmd_get_output_scriptpubkey_reply */
bool fromwire_hsmd_get_output_scriptpubkey_reply(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, u8 **script UNNEEDED)
{ fprintf(stderr, "fromwire_hsmd_get_output_scriptpubkey_reply called!\n"); abort(); }
/* Generated stub for get_channel_basepoints */
void get_channel_basepoints(struct lightningd *ld UNNEEDED,
			    const struct node_id *peer_id UNNEEDED,
			    const u64 dbid UNNEEDED,
			    struct basepoints *local_basepoints UNNEEDED,
			    struct pubkey *local_funding_pubkey UNNEEDED)
{ fprintf(stderr, "get_channel_basepoints called!\n"); abort(); }
/* Generated stub for logv */
void logv(struct log *log UNNEEDED, enum log_level level UNNEEDED, const struct node_id *node_id UNNEEDED,
	  bool call_notifier UNNEEDED, const char *fmt UNNEEDED, va_list ap UNNEEDED)
{ fprintf(stderr, "logv called!\n"); abort(); }
/* Generated stub for psbt_fixup */
const u8 *psbt_fixup(const tal_t *ctx UNNEEDED, const u8 *psbtblob UNNEEDED)
{ fprintf(stderr, "psbt_fixup called!\n"); abort(); }
/* Generated stub for towire_hsmd_get_channel_basepoints */
u8 *towire_hsmd_get_channel_basepoints(const tal_t *ctx UNNEEDED, const struct node_id *peerid UNNEEDED, u64 dbid UNNEEDED)
{ fprintf(stderr, "towire_hsmd_get_channel_basepoints called!\n"); abort(); }
/* Generated stub for towire_hsmd_get_output_scriptpubkey */
u8 *towire_hsmd_get_output_scriptpubkey(const tal_t *ctx UNNEEDED, u64 channel_id UNNEEDED, const struct node_id *peer_id UNNEEDED, const struct pubkey *commitment_point UNNEEDED)
{ fprintf(stderr, "towire_hsmd_get_output_scriptpubkey called!\n"); abort(); }
/* Generated stub for wire_sync_read */
u8 *wire_sync_read(const tal_t *ctx UNNEEDED, int fd UNNEEDED)
{ fprintf(stderr, "wire_sync_read called!\n"); abort(); }
/* Generated stub for wire_sync_write */
bool wire_sync_write(int fd UNNEEDED, const void *msg TAKES UNNEEDED)
{ fprintf(stderr, "wire_sync_write called!\n"); abort(); }
/* Generated stub for strip_lightning_prefix */
const char *to_canonical_invstr(const tal_t *ctx, const char *invstring UNNEEDED)
{ fprintf(stderr, "strip_lightning_prefix called!\n"); abort(); }
/* AUTOGENERATED MOCKS END */

void plugin_hook_db_sync(struct db *db UNNEEDED)
{
}

static struct db *create_test_db(void)
{
	struct db *db;
	char *dsn, *filename;

	int fd = tmpdir_mkstemp(tmpctx, "ldb-XXXXXX", &filename);
	if (fd == -1)
		return NULL;
	close(fd);

	dsn = tal_fmt(NULL, "sqlite3://%s", filename);
	tal_free(filename);
	db = db_open(NULL, dsn, db_error, (struct lightningd *)NULL);
	db->data_version = 0;
	db->report_changes_fn = NULL;

	tal_free(dsn);
	return db;
}

static bool test_empty_db_migrate(struct lightningd *ld)
{
	struct db *db = create_test_db();
	const struct ext_key *bip32_base = NULL;
	CHECK(db);
	db_begin_transaction(db);
	CHECK(db_get_version(db) == -1);
	db_migrate(ld, db, bip32_base);
	db_commit_transaction(db);

	db_begin_transaction(db);
	CHECK(db_get_version(db) == ARRAY_SIZE(dbmigrations) - 1);
	db_commit_transaction(db);

	tal_free(db);
	return true;
}

static bool test_primitives(void)
{
	struct db_stmt *stmt;
	struct db *db = create_test_db();

	db_begin_transaction(db);
	CHECK(db->in_transaction);
	db_commit_transaction(db);
	CHECK(!db->in_transaction);
	db_begin_transaction(db);
	db_commit_transaction(db);

	db_begin_transaction(db);
	stmt = db_prepare_v2(db, SQL("SELECT name FROM sqlite_master WHERE type='table';"));
	db_exec_prepared_v2(stmt);
	tal_free(stmt);

	/* We didn't migrate the DB, so don't have the vars table. Pretend we
	 * didn't change anything so we don't bump the data_version. */
	db->dirty = false;
	db_commit_transaction(db);
	CHECK(!db->in_transaction);
	tal_free(db);

	return true;
}

static bool test_vars(struct lightningd *ld)
{
	struct db *db = create_test_db();
	char *varname = "testvar";
	const struct ext_key *bip32_base = NULL;
	CHECK(db);

	db_begin_transaction(db);
	db_migrate(ld, db, bip32_base);
	/* Check default behavior */
	CHECK(db_get_intvar(db, varname, 42) == 42);

	/* Check setting and getting */
	db_set_intvar(db, varname, 1);
	CHECK(db_get_intvar(db, varname, 42) == 1);

	/* Check updating */
	db_set_intvar(db, varname, 2);
	CHECK(db_get_intvar(db, varname, 42) == 2);
	db_commit_transaction(db);

	tal_free(db);
	return true;
}

static bool test_manip_columns(void)
{
	struct db_stmt *stmt;
	struct db *db = create_test_db();
	const char *field1 = "field1";

	db_begin_transaction(db);
	/* tablea refers to tableb */
	stmt = db_prepare_v2(db, SQL("CREATE TABLE tablea ("
				     "  id BIGSERIAL"
				     ", field1 INTEGER"
				     ", PRIMARY KEY (id))"));
	db_exec_prepared_v2(stmt);
	tal_free(stmt);

	stmt = db_prepare_v2(db, SQL("INSERT INTO tablea (id, field1) VALUES (0, 1);"));
	db_exec_prepared_v2(stmt);
	tal_free(stmt);

	stmt = db_prepare_v2(db, SQL("CREATE TABLE tableb ("
				     "   id REFERENCES tablea(id) ON DELETE CASCADE"
				     ",  field1 INTEGER"
				     ",  field2 INTEGER);"));
	db_exec_prepared_v2(stmt);
	tal_free(stmt);

	stmt = db_prepare_v2(db, SQL("INSERT INTO tableb (id, field1, field2) VALUES (0, 1, 2);"));
	db_exec_prepared_v2(stmt);
	tal_free(stmt);

	/* Needs vars table, since this changes db. */
	stmt = db_prepare_v2(db, SQL("CREATE TABLE vars (name VARCHAR(32), intval);"));
	db_exec_prepared_v2(stmt);
	tal_free(stmt);
	stmt = db_prepare_v2(db, SQL("INSERT INTO vars VALUES ('data_version', 0);"));
	db_exec_prepared_v2(stmt);
	tal_free(stmt);

	/* Rename tablea.field1 -> table1.field1a. */
	CHECK(db->config->rename_column(db, "tablea", "field1", "field1a"));
	/* Remove tableb.field1. */
	CHECK(db->config->delete_columns(db, "tableb", &field1, 1));

	stmt = db_prepare_v2(db, SQL("SELECT id, field1a FROM tablea;"));
	CHECK_MSG(db_query_prepared_canfail(stmt), "db_query_prepared must succeed");
	CHECK(db_step(stmt));
	CHECK(db_col_u64(stmt, "id") == 0);
	CHECK(db_col_u64(stmt, "field1a") == 1);
	CHECK(!db_step(stmt));
	tal_free(stmt);

	stmt = db_prepare_v2(db, SQL("SELECT id, field2 FROM tableb;"));
	CHECK_MSG(db_query_prepared_canfail(stmt), "db_query_prepared must succeed");
	CHECK(db_step(stmt));
	CHECK(db_col_u64(stmt, "id") == 0);
	CHECK(db_col_u64(stmt, "field2") == 2);
	CHECK(!db_step(stmt));
	tal_free(stmt);
	db->dirty = false;
	db->changes = tal_arr(db, const char *, 0);
	db_commit_transaction(db);

	db_begin_transaction(db);
	/* This will actually fail */
	stmt = db_prepare_v2(db, SQL("SELECT field1 FROM tablea;"));
	CHECK_MSG(!db_query_prepared_canfail(stmt), "db_query_prepared must fail");
	db->dirty = false;
	db->changes = tal_arr(db, const char *, 0);
	db_commit_transaction(db);

	db_begin_transaction(db);
	/* This will actually fail */
	stmt = db_prepare_v2(db, SQL("SELECT field1 FROM tableb;"));
	CHECK_MSG(!db_query_prepared_canfail(stmt), "db_query_prepared must fail");
	db->dirty = false;
	db->changes = tal_arr(db, const char *, 0);
	db_commit_transaction(db);

	tal_free(db);
	return true;
}

int main(int argc, char *argv[])
{
	bool ok = true;
	/* Dummy for migration hooks */
	struct lightningd *ld = tal(NULL, struct lightningd);

	common_setup(argv[0]);
	ld->config = test_config;

	/* We do a runtime test here, so we still check compile! */
	if (HAVE_SQLITE3) {
		ok &= test_empty_db_migrate(ld);
		ok &= test_vars(ld);
		ok &= test_primitives();
		ok &= test_manip_columns();
	}

	tal_free(ld);
	common_shutdown();
	return !ok;
}
