/*
 * Purple - Internet Messaging Library
 * Copyright (C) Pidgin Developers <devel@pidgin.im>
 *
 * Purple is the legal property of its developers, whose names are too numerous
 * to list here.  Please refer to the COPYRIGHT file distributed with this
 * source distribution.
 *
 * This program 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 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, see <https://www.gnu.org/licenses/>.
 */

#include <glib.h>

#include <birb.h>

#include <seagull.h>

#include <purple.h>

#define TEST_PURPLE_ACCOUNT_MANAGER_SEAGULL_BACKEND_ERROR (g_quark_from_static_string("test-account-manager-seagull-backend_error"))

/******************************************************************************
 * Tests
 *****************************************************************************/
static void
test_purple_account_manager_seagull_backend_new(void) {
	PurpleAccountManagerBackend *backend = NULL;
	GError *error = NULL;

	backend = purple_account_manager_seagull_backend_new(NULL, &error);
	g_assert_no_error(error);

	birb_assert_type(backend, PURPLE_TYPE_ACCOUNT_MANAGER_SEAGULL_BACKEND);

	g_assert_finalize_object(backend);
}

/******************************************************************************
 * Add/Remove Test
 *****************************************************************************/
static void
test_purple_account_manager_seagull_backend_load_accounts_empty_cb(GObject *source,
                                                                   GAsyncResult *result,
                                                                   G_GNUC_UNUSED gpointer data)
{
	GError *error = NULL;
	GListModel *accounts = NULL;

	birb_assert_type(source, PURPLE_TYPE_ACCOUNT_MANAGER_SEAGULL_BACKEND);

	accounts = purple_account_manager_backend_load_accounts_finish(PURPLE_ACCOUNT_MANAGER_BACKEND(source),
	                                                               result,
	                                                               &error);
	g_assert_no_error(error);
	g_assert_true(G_IS_LIST_MODEL(accounts));

	g_assert_cmpuint(g_list_model_get_n_items(accounts), ==, 0);

	g_assert_finalize_object(accounts);
}

static void
test_purple_account_manager_seagull_backend_load_accounts_single_cb(GObject *source,
                                                                    GAsyncResult *result,
                                                                    gpointer data)
{
	PurpleAccountManagerBackend *backend = NULL;
	PurpleAccount *actual = NULL;
	PurpleAccount *expected = data;
	PurpleAccountSettings *actual_settings = NULL;
	PurpleAccountSettings *expected_settings = NULL;
	PurpleContactInfo *actual_info = NULL;
	PurpleContactInfo *expected_info = NULL;
	PurpleTags *actual_tags = NULL;
	PurpleTags *expected_tags = NULL;
	GError *error = NULL;
	GError *actual_error = NULL;
	GError *expected_error = NULL;
	GListModel *accounts = NULL;
	GTimeZone *actual_time_zone = NULL;
	GTimeZone *expected_time_zone = NULL;
	guint actual_n_items = 0;
	guint expected_n_items = 0;

	birb_assert_type(source, PURPLE_TYPE_ACCOUNT_MANAGER_SEAGULL_BACKEND);
	backend = PURPLE_ACCOUNT_MANAGER_BACKEND(source);

	accounts = purple_account_manager_backend_load_accounts_finish(backend,
	                                                               result,
	                                                               &error);
	g_assert_no_error(error);
	g_assert_true(G_IS_LIST_MODEL(accounts));

	birb_assert_list_model_n_items(G_LIST_MODEL(accounts), 1);

	actual = g_list_model_get_item(G_LIST_MODEL(accounts), 0);
	birb_assert_type(actual, PURPLE_TYPE_ACCOUNT);

	/* Run through the account properties. */
	g_assert_true(purple_account_get_enabled(actual) ==
	              purple_account_get_enabled(expected));

	actual_error = purple_account_get_error(actual);
	expected_error = purple_account_get_error(expected);
	g_assert_error(actual_error, expected_error->domain, expected_error->code);

	g_assert_cmpstr(purple_account_get_id(actual), ==,
	                purple_account_get_id(expected));

	g_assert_cmpstr(purple_account_get_name(actual), ==,
	                purple_account_get_name(expected));

	g_assert_cmpstr(purple_account_get_protocol_id(actual), ==,
	                purple_account_get_protocol_id(expected));

	g_assert_true(purple_account_get_remember_password(actual) ==
	              purple_account_get_remember_password(expected));

	g_assert_true(purple_account_get_enabled(actual) ==
	              purple_account_get_enabled(expected));

	g_assert_true(purple_account_get_require_password(actual) ==
	              purple_account_get_require_password(expected));

	g_assert_cmpstr(purple_account_get_user_info(actual), ==,
	                purple_account_get_user_info(expected));

	/* Run through the contact info's. */
	actual_info = purple_account_get_contact_info(actual);
	expected_info = purple_account_get_contact_info(expected);

	g_assert_cmpstr(purple_contact_info_get_alias(actual_info), ==,
	                purple_contact_info_get_alias(expected_info));

	g_assert_cmpstr(purple_contact_info_get_color(actual_info), ==,
	                purple_contact_info_get_color(expected_info));

	g_assert_cmpstr(purple_contact_info_get_email(actual_info), ==,
	                purple_contact_info_get_email(expected_info));

	g_assert_true(purple_contact_info_get_external(actual_info) ==
	              purple_contact_info_get_external(expected_info));

	g_assert_true(purple_contact_info_get_favorite(actual_info) ==
	              purple_contact_info_get_favorite(expected_info));

	g_assert_cmpstr(purple_contact_info_get_id(actual_info), ==,
	                purple_contact_info_get_id(expected_info));

	g_assert_cmpstr(purple_contact_info_get_note(actual_info), ==,
	                purple_contact_info_get_note(expected_info));

	g_assert_cmpstr(purple_contact_info_get_phone_number(actual_info), ==,
	                purple_contact_info_get_phone_number(expected_info));

	g_assert_cmpstr(purple_contact_info_get_sid(actual_info), ==,
	                purple_contact_info_get_sid(expected_info));

	actual_time_zone = purple_contact_info_get_time_zone(actual_info);
	expected_time_zone = purple_contact_info_get_time_zone(expected_info);
	g_assert_cmpstr(g_time_zone_get_identifier(actual_time_zone), ==,
	                g_time_zone_get_identifier(expected_time_zone));

#pragma message "Fix this when badges are persistent."
#if 0
	actual_badges = purple_contact_info_get_badges(actual_info);
	expected_badges = purple_contact_info_get_badges(expected_info);
	g_assert_cmpint(purple_badges_compare(actual_badges, expected_badges), ==,
	                0);
#endif

	/* Verify the tags. */
	actual_tags = purple_contact_info_get_tags(actual_info);
	expected_tags = purple_contact_info_get_tags(expected_info);

	g_assert_true(purple_tags_contains(actual_tags, expected_tags));

	/* Verify the settings. */
	actual_settings = purple_account_get_settings(actual);
	expected_settings = purple_account_get_settings(expected);

	actual_n_items = g_list_model_get_n_items(G_LIST_MODEL(actual_settings));
	expected_n_items = g_list_model_get_n_items(G_LIST_MODEL(expected_settings));
	g_assert_cmpuint(actual_n_items, ==, expected_n_items);

	for(guint i = 0; i < expected_n_items; i++) {
		PurpleAccountSetting *actual_setting = NULL;
		PurpleAccountSetting *expected_setting = NULL;

		actual_setting = g_list_model_get_item(G_LIST_MODEL(actual_settings),
		                                       i);
		expected_setting = g_list_model_get_item(G_LIST_MODEL(expected_settings),
		                                         i);

		g_assert_cmpuint(G_OBJECT_TYPE(actual_setting), ==,
		                 G_OBJECT_TYPE(expected_setting));

		if(PURPLE_IS_ACCOUNT_SETTING_BOOLEAN(expected_setting)) {
			gboolean actual_value = FALSE;
			gboolean expected_value = FALSE;

			actual_value = purple_account_setting_boolean_get_value(PURPLE_ACCOUNT_SETTING_BOOLEAN(actual_setting));
			expected_value = purple_account_setting_boolean_get_value(PURPLE_ACCOUNT_SETTING_BOOLEAN(expected_setting));

			g_assert_true(actual_value == expected_value);
		} else if(PURPLE_IS_ACCOUNT_SETTING_INT(expected_setting)) {
			int actual_value = FALSE;
			int expected_value = FALSE;

			actual_value = purple_account_setting_int_get_value(PURPLE_ACCOUNT_SETTING_INT(actual_setting));
			expected_value = purple_account_setting_int_get_value(PURPLE_ACCOUNT_SETTING_INT(expected_setting));

			g_assert_cmpint(actual_value, ==, expected_value);
		} else if(PURPLE_IS_ACCOUNT_SETTING_STRING(expected_setting)) {
			const char *actual_value = FALSE;
			const char *expected_value = FALSE;

			actual_value = purple_account_setting_string_get_value(PURPLE_ACCOUNT_SETTING_STRING(actual_setting));
			expected_value = purple_account_setting_string_get_value(PURPLE_ACCOUNT_SETTING_STRING(expected_setting));

			g_assert_cmpstr(actual_value, ==, expected_value);
		} else if(PURPLE_IS_ACCOUNT_SETTING_STRING_LIST(expected_setting)) {
			const char *actual_value = FALSE;
			const char *expected_value = FALSE;

			actual_value = purple_account_setting_string_list_get_active_item(PURPLE_ACCOUNT_SETTING_STRING_LIST(actual_setting));
			expected_value = purple_account_setting_string_list_get_active_item(PURPLE_ACCOUNT_SETTING_STRING_LIST(expected_setting));

			g_assert_cmpstr(actual_value, ==, expected_value);
		} else {
			g_assert_not_reached();
		}

		g_clear_object(&actual_setting);
		g_clear_object(&expected_setting);
	}

	/* Cleanup. */
	g_assert_finalize_object(accounts);
	g_assert_finalize_object(actual);
}

static void
test_purple_account_manager_seagull_backend_save_cb(GObject *source,
                                                    GAsyncResult *result,
                                                    gpointer data)
{
	PurpleAccountManagerBackend *backend = NULL;
	GError *error = NULL;
	gboolean success = FALSE;

	backend = PURPLE_ACCOUNT_MANAGER_BACKEND(source);
	success = purple_account_manager_backend_save_account_finish(backend,
	                                                             result,
	                                                             &error);
	g_assert_no_error(error);
	g_assert_true(success);

	purple_account_manager_backend_load_accounts_async(backend, NULL,
	                                                   test_purple_account_manager_seagull_backend_load_accounts_single_cb,
	                                                   data);

	g_main_context_iteration(NULL, FALSE);
}

static void
test_purple_account_manager_seagull_backend_delete_cb(GObject *source,
                                                      GAsyncResult *result,
                                                      G_GNUC_UNUSED gpointer data)
{
	PurpleAccountManagerBackend *backend = NULL;
	GError *error = NULL;
	gboolean success = FALSE;

	backend = PURPLE_ACCOUNT_MANAGER_BACKEND(source);
	success = purple_account_manager_backend_delete_account_finish(backend,
	                                                               result,
	                                                               &error);
	g_assert_no_error(error);
	g_assert_true(success);

	purple_account_manager_backend_load_accounts_async(backend, NULL,
	                                                   test_purple_account_manager_seagull_backend_load_accounts_empty_cb,
	                                                   NULL);

	g_main_context_iteration(NULL, FALSE);
}

static void
test_purple_account_manager_seagull_backend_add_remove(void) {
	PurpleAccount *account = NULL;
	PurpleAccountManagerBackend *backend = NULL;
	PurpleAccountSetting *setting = NULL;
	PurpleAccountSettings *settings = NULL;
	PurpleBadges *badges = NULL;
	PurpleBadge *badge = NULL;
	PurpleContactInfo *info = NULL;
	PurpleTags *tags = NULL;
	GError *account_error = NULL;
	GError *error = NULL;
	GTimeZone *tz = NULL;

	backend = purple_account_manager_seagull_backend_new(NULL, &error);
	g_assert_no_error(error);
	birb_assert_type(backend, PURPLE_TYPE_ACCOUNT_MANAGER_SEAGULL_BACKEND);

	account_error = g_error_new_literal(TEST_PURPLE_ACCOUNT_MANAGER_SEAGULL_BACKEND_ERROR,
	                                    404, "account not found");

	/* Create the account and save it to the backend. */
	account = g_object_new(
		PURPLE_TYPE_ACCOUNT,
		"enabled", TRUE,
		"error", account_error,
		"id", "id",
		"name", "test-account",
		"protocol-id", "protocol",
		"remember-password", TRUE,
		"require-password", TRUE,
		"user-info", "user-info",
		NULL);
	g_clear_error(&account_error);

	info = purple_account_get_contact_info(account);
	purple_contact_info_set_alias(info, "contact-alias");
	purple_contact_info_set_color(info, "contact-color");
	purple_contact_info_set_display_name(info, "contact-display-name");
	purple_contact_info_set_email(info, "contact-email");
	purple_contact_info_set_external(info, TRUE);
	purple_contact_info_set_favorite(info, TRUE);
	purple_contact_info_set_id(info, "contact-id");
	purple_contact_info_set_note(info, "contact-note");
	purple_contact_info_set_phone_number(info, "contact-phone-number");
	purple_contact_info_set_sid(info, "contact-sid");
	tz = g_time_zone_new_identifier("-05:00");
	purple_contact_info_set_time_zone(info, tz);
	g_clear_pointer(&tz, g_time_zone_unref);

	badges = purple_contact_info_get_badges(info);
	badge = purple_badge_new("test-badge", 0, "test", "t");
	purple_badges_add_badge(badges, badge);
	g_clear_object(&badge);

	tags = purple_contact_info_get_tags(info);
	purple_tags_add(tags, "test=1");

	settings = purple_account_get_settings(account);

	setting = purple_account_setting_boolean_new("test-bool", "test bool",
	                                             TRUE);
	purple_account_settings_add_setting(settings, setting);

	setting = purple_account_setting_int_new("test-int", "test int", 1337);
	purple_account_settings_add_setting(settings, setting);

	setting = purple_account_setting_string_new("test-string", "test string",
	                                            "abc123");
	purple_account_settings_add_setting(settings, setting);

	setting = purple_account_setting_string_list_new("test-string-list",
	                                                 "test string list");
	purple_account_setting_string_list_add_item(PURPLE_ACCOUNT_SETTING_STRING_LIST(setting),
	                                            "foo", "Foo");
	purple_account_setting_string_list_add_item(PURPLE_ACCOUNT_SETTING_STRING_LIST(setting),
	                                            "bar", "Bar");
	purple_account_setting_string_list_add_item(PURPLE_ACCOUNT_SETTING_STRING_LIST(setting),
	                                            "baz", "Baz");
	purple_account_setting_string_list_set_active_item(PURPLE_ACCOUNT_SETTING_STRING_LIST(setting),
	                                                   "bar");
	purple_account_settings_add_setting(settings, setting);

	/* Make sure that there's nothing to load. */
	purple_account_manager_backend_load_accounts_async(backend, NULL,
	                                                   test_purple_account_manager_seagull_backend_load_accounts_empty_cb,
	                                                   NULL);

	g_main_context_iteration(NULL, FALSE);

	/* Save the account. */
	purple_account_manager_backend_save_account_async(backend, account, NULL,
	                                                  test_purple_account_manager_seagull_backend_save_cb,
	                                                  account);

	g_main_context_iteration(NULL, FALSE);

	/* Delete the account from the backend. */
	purple_account_manager_backend_delete_account_async(backend, account, NULL,
	                                                    test_purple_account_manager_seagull_backend_delete_cb,
	                                                    NULL);

	g_main_context_iteration(NULL, FALSE);

	/* Cleanup. */
	g_assert_finalize_object(backend);
	g_assert_finalize_object(account);
}

/******************************************************************************
 * Public API
 *****************************************************************************/
int
main(int argc, char **argv) {
	g_test_init(&argc, &argv, NULL);
	g_test_set_nonfatal_assertions();

	g_test_add_func("/accountmanagerbackend/seagull/new",
	                test_purple_account_manager_seagull_backend_new);

	g_test_add_func("/accountmanagerbackend/seagull/add-remove",
	                test_purple_account_manager_seagull_backend_add_remove);

	return g_test_run();
}
