/*
 * Copyright (C) 2022-2025 Seagull Developers
 *
 * Seagull is the legal property of its developers, whose names are too
 * numerous to list here. Please refer to the AUTHORS file distributed
 * with this source distribution
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <https://www.gnu.org/licenses/>.
 */

#include <glib.h>

#include <seagull.h>

/******************************************************************************
 * get schema version tests
 *****************************************************************************/
static void
test_seagull_migrations_get_schema_version_null(void) {
	if(g_test_subprocess()) {
		GError *error = NULL;
		int version = 0;

		version = seagull_migrations_get_schema_version(NULL, &error);

		/* This code is never actually run, it's here for completeness. */
		g_assert_error(error,
		               SEAGULL_MIGRATIONS_ERROR,
		               SEAGULL_MIGRATIONS_ERROR_EXECUTION_FAILED);
		g_clear_error(&error);
		g_assert_cmpint(version, ==, -1);
	}

	g_test_trap_subprocess(NULL, 0, 0);
	g_test_trap_assert_failed();
	g_test_trap_assert_stderr("*assertion*!= NULL*");
}

static void
test_seagull_migrations_get_schema_version_new(void) {
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	int version = 0;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	version = seagull_migrations_get_schema_version(db, &error);
	g_assert_no_error(error);
	g_assert_cmpint(version, ==, 0);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

/******************************************************************************
 * string migration tests
 *****************************************************************************/
static void
test_seagull_migrations_run_from_string_null(void) {
	if(g_test_subprocess()) {
		SeagullSqlite3 *db = NULL;
		GError *error = NULL;
		gboolean result = FALSE;

		db = seagull_db_new_in_memory(&error);
		g_assert_no_error(error);
		g_assert_nonnull(db);

		result = seagull_migrations_run_from_strings(db, NULL, &error);
		g_assert_no_error(error);
		g_assert_false(result);

		/* This code will never be reached, it's just here for completeness. */
		result = seagull_db_close(db, &error);
		g_assert_no_error(error);
		g_assert_true(result);
	}

	g_test_trap_subprocess(NULL, 0, 0);
	g_test_trap_assert_failed();
	g_test_trap_assert_stderr("*migrations != NULL*");
}

static void
test_seagull_migrations_run_from_string_null_terminator(void) {
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;
	int version = -1;
	const char *migrations[] = {NULL};

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_strings(db, migrations, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	version = seagull_migrations_get_schema_version(db, &error);
	g_assert_no_error(error);
	g_assert_cmpint(version, ==, 0);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_migrations_run_from_string_multiple(void) {
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;
	int version = -1;
	const char *migrations[] = {
		"CREATE TABLE foo(a TEXT); CREATE TABLE bar(b TEXT);",
		"CREATE TABLE baz(c TEXT);",
		NULL
	};

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_strings(db, migrations, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	version = seagull_migrations_get_schema_version(db, &error);
	g_assert_no_error(error);
	g_assert_cmpint(version, ==, 2);

	/* Run the migrations again and make sure we remain at schema version 2. */
	result = seagull_migrations_run_from_strings(db, migrations, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	version = seagull_migrations_get_schema_version(db, &error);
	g_assert_no_error(error);
	g_assert_cmpint(version, ==, 2);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_migrations_run_from_string_syntax_error(void) {
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;
	int version = -1;
	const char *migrations[] = {
		"CREATE TABLE broke(a TEXT",
		NULL
	};

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_strings(db, migrations, &error);
	g_assert_error(error,
	               SEAGULL_MIGRATIONS_ERROR,
	               SEAGULL_MIGRATIONS_ERROR_EXECUTION_FAILED);
	g_clear_error(&error);
	g_assert_false(result);

	version = seagull_migrations_get_schema_version(db, &error);
	g_assert_no_error(error);
	g_assert_cmpint(version, ==, 0);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_migrations_run_from_string_older(void) {
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;
	int version = -1;
	const char *migrations1[] = {
		"CREATE TABLE foo(a TEXT); CREATE TABLE bar(b TEXT);",
		"CREATE TABLE baz(c TEXT);",
		NULL
	};
	const char *migrations2[] = {
		"CREATE TABLE foo(a TEXT); CREATE TABLE bar(b TEXT);",
		NULL
	};

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_strings(db, migrations1, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	version = seagull_migrations_get_schema_version(db, &error);
	g_assert_no_error(error);
	g_assert_cmpint(version, ==, 2);

	/* Run the older migrations now and verify we get a failure. */
	result = seagull_migrations_run_from_strings(db, migrations2, &error);
	g_assert_error(error,
	               SEAGULL_MIGRATIONS_ERROR,
	               SEAGULL_MIGRATIONS_ERROR_SCHEMA_TOO_NEW);
	g_clear_error(&error);
	g_assert_false(result);

	version = seagull_migrations_get_schema_version(db, &error);
	g_assert_no_error(error);
	g_assert_cmpint(version, ==, 2);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

/******************************************************************************
 * resource migration tests
 *****************************************************************************/
static void
test_seagull_migrations_run_from_resource_null_path(void) {
	if(g_test_subprocess()) {
		SeagullSqlite3 *db = NULL;
		GError *error = NULL;
		gboolean result = FALSE;

		db = seagull_db_new_in_memory(&error);
		g_assert_no_error(error);
		g_assert_nonnull(db);

		result = seagull_migrations_run_from_resources(db, NULL, NULL, &error);
		g_assert_no_error(error);
		g_assert_false(result);

		result = seagull_db_close(db, &error);
		g_assert_no_error(error);
		g_assert_true(result);
	}

	g_test_trap_subprocess(NULL, 0, 0);
	g_test_trap_assert_failed();
	g_test_trap_assert_stderr("*path != NULL*");
}

static void
test_seagull_migrations_run_from_resource_null_migrations(void) {
	if(g_test_subprocess()) {
		SeagullSqlite3 *db = NULL;
		GError *error = NULL;
		gboolean result = FALSE;
		const char *path = "/org/imfreedom/keep/seagull/seagull/tests/migrations/";

		db = seagull_db_new_in_memory(&error);
		g_assert_no_error(error);
		g_assert_nonnull(db);

		result = seagull_migrations_run_from_resources(db, path, NULL, &error);
		g_assert_no_error(error);
		g_assert_false(result);

		result = seagull_db_close(db, &error);
		g_assert_no_error(error);
		g_assert_true(result);
	}

	g_test_trap_subprocess(NULL, 0, 0);
	g_test_trap_assert_failed();
	g_test_trap_assert_stderr("*migrations != NULL*");
}

static void
test_seagull_migrations_run_from_resource_null_terminator(void) {
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;
	int version = -1;
	const char *migrations[] = {NULL};
	const char *path = "/org/imfreedom/keep/seagull/seagull/tests/migrations/";

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_resources(db, path, migrations,
	                                               &error);
	g_assert_no_error(error);
	g_assert_true(result);

	version = seagull_migrations_get_schema_version(db, &error);
	g_assert_no_error(error);
	g_assert_cmpint(version, ==, 0);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_migrations_run_from_resource_multiple(void) {
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;
	int version = -1;
	const char *migrations[] = {"initial.sql", "secondary.sql", NULL};
	const char *path = "/org/imfreedom/keep/seagull/seagull/tests/migrations/";

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_resources(db, path, migrations,
	                                               &error);
	g_assert_no_error(error);
	g_assert_true(result);

	version = seagull_migrations_get_schema_version(db, &error);
	g_assert_no_error(error);
	g_assert_cmpint(version, ==, 2);

	/* Run the migrations again and make sure we remain at schema version 2. */
	result = seagull_migrations_run_from_strings(db, migrations, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	version = seagull_migrations_get_schema_version(db, &error);
	g_assert_no_error(error);
	g_assert_cmpint(version, ==, 2);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_migrations_run_from_resource_missing(void) {
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;
	int version = -1;
	const char *migrations[] = {"initial.sql", "imaginary.sql", NULL};
	const char *path = "/org/imfreedom/keep/seagull/seagull/tests/migrations/";

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_resources(db, path, migrations,
	                                               &error);
	g_assert_error(error,
	               G_RESOURCE_ERROR,
	               G_RESOURCE_ERROR_NOT_FOUND);
	g_clear_error(&error);
	g_assert_false(result);

	version = seagull_migrations_get_schema_version(db, &error);
	g_assert_no_error(error);
	g_assert_cmpint(version, ==, 1);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_migrations_run_from_resource_syntax_error(void) {
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;
	int version = -1;
	const char *migrations[] = {"malformed.sql", NULL};
	const char *path = "/org/imfreedom/keep/seagull/seagull/tests/migrations/";

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_resources(db, path, migrations,
	                                               &error);
	g_assert_error(error,
	               SEAGULL_MIGRATIONS_ERROR,
	               SEAGULL_MIGRATIONS_ERROR_EXECUTION_FAILED);
	g_clear_error(&error);
	g_assert_false(result);

	version = seagull_migrations_get_schema_version(db, &error);
	g_assert_no_error(error);
	g_assert_cmpint(version, ==, 0);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_migrations_run_from_resource_older(void) {
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;
	int version = -1;
	const char *migrations1[] = {"initial.sql", "secondary.sql", NULL};
	const char *migrations2[] = {"initial.sql", NULL};
	const char *path = "/org/imfreedom/keep/seagull/seagull/tests/migrations/";

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_resources(db, path, migrations1,
	                                               &error);
	g_assert_no_error(error);
	g_assert_true(result);

	version = seagull_migrations_get_schema_version(db, &error);
	g_assert_no_error(error);
	g_assert_cmpint(version, ==, 2);

	/* Run the older migrations now and verify we get a failure. */
	result = seagull_migrations_run_from_resources(db, path, migrations2,
	                                               &error);
	g_assert_error(error,
	               SEAGULL_MIGRATIONS_ERROR,
	               SEAGULL_MIGRATIONS_ERROR_SCHEMA_TOO_NEW);
	g_clear_error(&error);
	g_assert_false(result);

	version = seagull_migrations_get_schema_version(db, &error);
	g_assert_no_error(error);
	g_assert_cmpint(version, ==, 2);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

/******************************************************************************
 * Main
 *****************************************************************************/
int
main(int argc, char *argv[]) {
	g_test_init(&argc, &argv, NULL);

	g_test_add_func("/migrations/schema_version/null",
	                test_seagull_migrations_get_schema_version_null);
	g_test_add_func("/migrations/schema_version/new",
	                test_seagull_migrations_get_schema_version_new);

	g_test_add_func("/migrations/run-from-string/null",
	                test_seagull_migrations_run_from_string_null);
	g_test_add_func("/migrations/run-from-string/null-terminator",
	                test_seagull_migrations_run_from_string_null_terminator);
	g_test_add_func("/migrations/run-from-string/multiple",
	                test_seagull_migrations_run_from_string_multiple);
	g_test_add_func("/migrations/run-from-string/syntax-error",
	                test_seagull_migrations_run_from_string_syntax_error);
	g_test_add_func("/migrations/run-from-string/older",
	                test_seagull_migrations_run_from_string_older);

	g_test_add_func("/migrations/run-from-resource/null-path",
	                test_seagull_migrations_run_from_resource_null_path);
	g_test_add_func("/migrations/run-from-resource/null-migrations",
	                test_seagull_migrations_run_from_resource_null_migrations);
	g_test_add_func("/migrations/run-from-resource/null-terminator",
	                test_seagull_migrations_run_from_resource_null_terminator);
	g_test_add_func("/migrations/run-from-resource/multiple",
	                test_seagull_migrations_run_from_resource_multiple);
	g_test_add_func("/migrations/run-from-resource/missing",
	                test_seagull_migrations_run_from_resource_missing);
	g_test_add_func("/migrations/run-from-resource/syntax-error",
	                test_seagull_migrations_run_from_resource_syntax_error);
	g_test_add_func("/migrations/run-from-resource/older",
	                test_seagull_migrations_run_from_resource_older);

	return g_test_run();
}
