/*
 * ministream - minimal (subset) appstream metadata parser
 * Copyright (c) 2026  Florian Leander Singer <sp1rit@disroot.org>
 *
 * 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 <gio/gio.h>
#include <locale.h>

#include <ministream.h>

#include "testfixtures.h"

static MsComponent* component_from_res(const gchar* path, GError** error) {
	GBytes* resource = g_resources_lookup_data(path, G_RESOURCE_LOOKUP_FLAGS_NONE, error);
	if (!resource)
		return NULL;

	gsize resource_len;
	const guchar* resource_data = g_bytes_get_data(resource, &resource_len);
	MsComponent* component = ms_component_new_from_xml_data(resource_data, resource_len, error);
	g_bytes_unref(resource);
	return component;
}

static void test_ms_valid(void) {
	GError* err = NULL;
	MsComponent* component = component_from_res("/arpa/sp1rit/ministream/tests/fixtures/valid.metainfo.xml", &err);
	g_assert_no_error(err);

	g_assert_cmpstr(ms_component_get_id(component), ==, "arpa.sp1rit.minitest.test.valid");
	g_assert_cmpstr(ms_component_get_name(component), ==, "Valid Component");
	g_assert_cmpstr(ms_component_get_homepage_uri(component), ==, "https://gitlab.gnome.org/sp1rit/ministream/");
	g_assert_cmpstr(ms_component_get_bugtracker_uri(component), ==, "https://gitlab.gnome.org/sp1rit/ministream/issues");
	g_assert_cmpstr(ms_component_get_help_uri(component), ==, "https://discourse.gnome.org");

	const gchar** launchables = ms_component_get_desktop_launchables(component);
	const gchar* expected_launchables[] = { "arpa.sp1rit.minitest.test.valid.alpha", "arpa.sp1rit.minitest.test.valid.beta", NULL };
	g_assert_cmpstrv(launchables, expected_launchables);

	guint n_releases;
	const MsRelease** releases = ms_component_get_releases(component, &n_releases);
	g_assert_cmpuint(n_releases, ==, 1);
	g_assert_cmpstr(ms_release_get_version(releases[0]), ==, "1.0");
	g_assert_cmpstr(ms_release_get_description(releases[0]), ==, "<p>Testing Build</p>\n");

	g_assert_cmpstr(ms_component_get_project_license(component), ==, "LGPL-2.1-or-later");
	g_assert_cmpstr(ms_component_get_developer_name(component), ==, "Florian \"sp1rit\"");

	ms_component_unref(component);
}

static void test_ms_upref(void) {
	GError* err = NULL;
	MsComponent* component = component_from_res("/arpa/sp1rit/ministream/tests/fixtures/valid.metainfo.xml", &err);
	g_assert_no_error(err);

	ms_component_ref(component);
	ms_component_unref(component);

	g_assert_cmpstr(ms_component_get_id(component), ==, "arpa.sp1rit.minitest.test.valid");

	ms_component_unref(component);
}

static void test_ms_minimal(void) {
	GError* err = NULL;
	MsComponent* component = component_from_res("/arpa/sp1rit/ministream/tests/fixtures/minimal.metainfo.xml", &err);
	g_assert_no_error(err);

	g_assert_cmpstr(ms_component_get_id(component), ==, "arpa.sp1rit.minitest.test.minimal");
	g_assert_null(ms_component_get_name(component));
	g_assert_null(ms_component_get_homepage_uri(component));
	g_assert_null(ms_component_get_bugtracker_uri(component));
	g_assert_null(ms_component_get_help_uri(component));
	g_assert_null(ms_component_get_desktop_launchables(component));

	guint n_releases;
	const MsRelease** releases = ms_component_get_releases(component, &n_releases);
	g_assert_cmpuint(n_releases, ==, 0);
	g_assert_null(releases);

	g_assert_null(ms_component_get_project_license(component));
	g_assert_null(ms_component_get_developer_name(component));

	ms_component_unref(component);
}

static void test_ms_versions(void) {
	GError* err = NULL;
	MsComponent* component = component_from_res("/arpa/sp1rit/ministream/tests/fixtures/versions.metainfo.xml", &err);
	g_assert_no_error(err);

	const gchar* expected[] = {
		NULL,
		NULL,
		"",
		"0.6.12a",
		"0.6.12b-d",
		"1",
		"1",
		"1.2a",
		"1.2.3~rc1",
		"1.2.3",
		"1.2.4",
		"11.0.9.1+1-0ubuntu1",
		"alpha",
		"beta",
		"1:0",
		"2:0"
	};

	guint n_releases;
	const MsRelease** releases = ms_component_get_releases(component, &n_releases);
	g_assert_cmpuint(n_releases, ==, G_N_ELEMENTS(expected));

	for (guint i = 0; i < G_N_ELEMENTS(expected); i++)
		g_assert_cmpstr(ms_release_get_version(releases[n_releases-i-1]), ==, expected[i]);
	
	ms_component_unref(component);
}

static void test_ms_developer(void) {
	GError* err = NULL;
	MsComponent* component = component_from_res("/arpa/sp1rit/ministream/tests/fixtures/developer.metainfo.xml", &err);
	g_assert_no_error(err);

	g_assert_cmpstr(ms_component_get_id(component), ==, "arpa.sp1rit.minitest.test.developer");
	g_assert_cmpstr(ms_component_get_developer_name(component), ==, "New");

	ms_component_unref(component);
}

static void test_ms_description(void) {
	GError* err = NULL;
	MsComponent* component = component_from_res("/arpa/sp1rit/ministream/tests/fixtures/description.metainfo.xml", &err);
	g_assert_no_error(err);

	guint n_releases;
	const MsRelease** releases = ms_component_get_releases(component, &n_releases);
	g_assert_cmpuint(n_releases, ==, 1);
	g_assert_cmpstr(ms_release_get_description(releases[0]), ==, ""
		"<p>Paragraph space</p>\n"
		"<ul>\n"
		"<li>List entry with <em>emph</em></li>\n"
		"</ul>\n"
		"<ol>\n"
		"<li>List entry with <code>code</code></li>\n"
		"</ol>\n");

	ms_component_unref(component);
}

static void test_ms_locale(void) {
	g_setenv("LC_MESSAGES", "en", TRUE);
	g_setenv("LANG", "en_US@latin.UTF-8", TRUE);

	GError* err = NULL;
	MsComponent* component = component_from_res("/arpa/sp1rit/ministream/tests/fixtures/locale.metainfo.xml", &err);
	g_assert_no_error(err);

	g_assert_cmpstr(ms_component_get_name(component), ==, "Translated");
	g_assert_cmpstr(ms_component_get_developer_name(component), ==, NULL);

	guint n_releases;
	const MsRelease** releases = ms_component_get_releases(component, &n_releases);
	g_assert_cmpuint(n_releases, ==, 1);
	g_assert_cmpstr(ms_release_get_description(releases[0]), ==, ""
		"<ul>\n"
		"<li>Ensure proper &lt;ul&gt; creation</li>\n"
		"</ul>\n"
		"<p>Next paragraph</p>\n"
		"<ol>\n"
		"<li>en_US</li>\n"
		"</ol>\n");

	ms_component_unref(component);
}

static void test_ms_misc(void) {
	GError* err = NULL;
	MsComponent* component = component_from_res("/arpa/sp1rit/ministream/tests/fixtures/misc.metainfo.xml", &err);
	g_assert_no_error(err);
	// No actual tests, just for increased coverage
	ms_component_unref(component);
}

static void test_ms_nocomponent(void) {
	GError* err = NULL;
	MsComponent* component = component_from_res("/arpa/sp1rit/ministream/tests/fixtures/nocomponent.metainfo.xml", &err);
	g_assert_null(component);
	g_assert_error(err, MS_ERROR, MS_ERROR_NO_COMPONENT);
	g_error_free(err);
}

static void test_ms_noid(void) {
	GError* err = NULL;
	MsComponent* component = component_from_res("/arpa/sp1rit/ministream/tests/fixtures/noid.metainfo.xml", &err);
	g_assert_null(component);
	g_assert_error(err, MS_ERROR, MS_ERROR_NO_IDENTIFIER);
	g_error_free(err);
}

static void test_ms_partial(void) {
	GError* err = NULL;
	MsComponent* component = component_from_res("/arpa/sp1rit/ministream/tests/fixtures/partial.metainfo.xml", &err);
	g_assert_null(component);
	g_assert_error(err, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE);
	g_error_free(err);
}

static void test_ms_warnings(void) {
	GError* err = NULL;
	g_test_expect_message("Ministream", G_LOG_LEVEL_WARNING, "Found multiple <id> nodes*");
	g_test_expect_message("Ministream", G_LOG_LEVEL_WARNING, "Found <node> while attempting to read string*");
	g_test_expect_message("Ministream", G_LOG_LEVEL_WARNING, "Unexpected tag <inv> in description*");
	g_test_expect_message("Ministream", G_LOG_LEVEL_WARNING, "Got invalid node <inv> in markup*");
	g_test_expect_message("Ministream", G_LOG_LEVEL_WARNING, "Found <release> with multiple <description> nodes*");
	g_test_expect_message("Ministream", G_LOG_LEVEL_WARNING, "Found unexpected node <notrelease> in <releases>*");
	g_test_expect_message("Ministream", G_LOG_LEVEL_WARNING, "Found multiple <releases> nodes*");
	g_test_expect_message("Ministream", G_LOG_LEVEL_WARNING, "Unexpected tag <invalid> in <developer>*");
	g_test_expect_message("Ministream", G_LOG_LEVEL_WARNING, "Found <url> without type attribute*");
	g_test_expect_message("Ministream", G_LOG_LEVEL_WARNING, "Found <launchable> without type attribute*");
	g_test_expect_message("Ministream", G_LOG_LEVEL_WARNING, "Found multiple <project_license> nodes*");
	g_test_expect_message("Ministream", G_LOG_LEVEL_WARNING, "Unexpected node type notcomponent as root node*");
	g_test_expect_message("Ministream", G_LOG_LEVEL_WARNING, "Multiple components in a single appstream manifest are not allowed*");
	MsComponent* component = component_from_res("/arpa/sp1rit/ministream/tests/fixtures/warnings.metainfo.xml", &err);
	g_assert_no_error(err);
	g_test_assert_expected_messages();
	ms_component_unref(component);
}

int main(int argc, char** argv) {
	setlocale(LC_ALL, "");

	g_resources_register(testfixtures_get_resource());
	g_test_init(&argc, &argv, NULL);

	g_test_add_func("/ministream/valid", test_ms_valid);
	g_test_add_func("/ministream/upref", test_ms_upref);
	g_test_add_func("/ministream/minimal", test_ms_minimal);
	g_test_add_func("/ministream/description", test_ms_description);
	g_test_add_func("/ministream/developer", test_ms_developer);
	g_test_add_func("/ministream/versions", test_ms_versions);
	g_test_add_func("/ministream/locale", test_ms_locale);
	g_test_add_func("/ministream/misc", test_ms_misc);
	g_test_add_func("/ministream/fail/nocomponent", test_ms_nocomponent);
	g_test_add_func("/ministream/fail/noid", test_ms_noid);
	g_test_add_func("/ministream/fail/partial", test_ms_partial);
	g_test_add_func("/ministream/warnings", test_ms_warnings);

	return g_test_run();
}
