/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
 *
 * Copyright 2025 GNOME Foundation, Inc.
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Authors:
 *  - Philip Withnall <pwithnall@gnome.org>
 */

#include "config.h"

#include <glib.h>
#include <gio/gio.h>
#include <libmalcontent/manager.h>
#include <libmalcontent/web-filter.h>
#include <libglib-testing/dbus-queue.h>
#include <locale.h>
#include <string.h>
#include "accounts-service-iface.h"
#include "accounts-service-extension-iface.h"


/* Test that the #GType definitions for various types work. */
static void
test_web_filter_types (void)
{
  g_type_ensure (mct_web_filter_get_type ());
  g_type_ensure (mct_web_filter_builder_get_type ());
}

/* Test that ref() and unref() work on an #MctWebFilter. */
static void
test_web_filter_refs (void)
{
  g_auto(MctWebFilterBuilder) builder = MCT_WEB_FILTER_BUILDER_INIT ();
  g_autoptr(MctWebFilter) filter = NULL;

  /* Use an empty #MctWebFilter. */
  filter = mct_web_filter_builder_end (&builder);

  g_assert_nonnull (filter);

  /* Call a method to check that the filter hasn’t been finalised. */
  g_assert_false (mct_web_filter_get_force_safe_search (filter));
  mct_web_filter_ref (filter);
  g_assert_false (mct_web_filter_get_force_safe_search (filter));
  mct_web_filter_unref (filter);
  g_assert_false (mct_web_filter_get_force_safe_search (filter));

  /* Final ref is dropped by g_autoptr(). */
}

/* Basic test of mct_web_filter_serialize() on a web filter. */
static void
test_web_filter_serialize (void)
{
  g_auto(MctWebFilterBuilder) builder = MCT_WEB_FILTER_BUILDER_INIT ();
  g_autoptr(MctWebFilter) filter = NULL;
  g_autoptr(GVariant) serialized = NULL;

  /* Use an empty #MctWebFilter. */
  filter = mct_web_filter_builder_end (&builder);

  /* We can’t assert anything about the serialisation format, since it’s opaque. */
  serialized = mct_web_filter_serialize (filter);
  g_assert_nonnull (serialized);
}

/* Basic test of mct_web_filter_deserialize() on various current and historic
 * serialised web filter variants. */
static void
test_web_filter_deserialize (void)
{
  /* These are all opaque. Older versions should be kept around to test
   * backwards compatibility. */
  const gchar *valid_web_filters[] =
    {
      "@a{sv} {}",
      "{ 'FilterType': <@u 0> }",
      "{ 'FilterType': <@u 1>, 'BlockLists': <{'easylist': 'https://v.firebog.net/hosts/Easylist.txt'}> }",
      "{ 'ForceSafeSearch': <true> }",
    };

  for (gsize i = 0; i < G_N_ELEMENTS (valid_web_filters); i++)
    {
      g_autoptr(GVariant) serialized = NULL;
      g_autoptr(MctWebFilter) filter = NULL;
      g_autoptr(GError) local_error = NULL;

      g_test_message ("%" G_GSIZE_FORMAT ": %s", i, valid_web_filters[i]);

      serialized = g_variant_parse (NULL, valid_web_filters[i], NULL, NULL, NULL);
      g_assert (serialized != NULL);

      filter = mct_web_filter_deserialize (serialized, 1, &local_error);
      g_assert_no_error (local_error);
      g_assert_nonnull (filter);
    }
}

/* Test of mct_web_filter_deserialize() on various invalid variants. */
static void
test_web_filter_deserialize_invalid (void)
{
  const gchar *invalid_web_filters[] =
    {
      "false",
      "()",
      "{ 'FilterType': <@u 600> }",
    };

  for (gsize i = 0; i < G_N_ELEMENTS (invalid_web_filters); i++)
    {
      g_autoptr(GVariant) serialized = NULL;
      g_autoptr(MctWebFilter) filter = NULL;
      g_autoptr(GError) local_error = NULL;

      g_test_message ("%" G_GSIZE_FORMAT ": %s", i, invalid_web_filters[i]);

      serialized = g_variant_parse (NULL, invalid_web_filters[i], NULL, NULL, NULL);
      g_assert (serialized != NULL);

      filter = mct_web_filter_deserialize (serialized, 1, &local_error);
      g_assert_error (local_error, MCT_MANAGER_ERROR, MCT_MANAGER_ERROR_INVALID_DATA);
      g_assert_null (filter);
    }
}

/* Test that mct_web_filter_equal() returns the correct results on various
 * web filters. */
static void
test_web_filter_equal (void)
{
  g_auto(MctWebFilterBuilder) builder = MCT_WEB_FILTER_BUILDER_INIT ();
  MctWebFilter *equal_filters[2];
  const char *unequal_filters_serialised[] =
    {
      "@a{sv} {}",
      "{ 'FilterType': <@u 1>, 'BlockLists': <{'easylist': 'https://v.firebog.net/hosts/Easylist.txt'}> }",
      "{ 'FilterType': <@u 1>, 'BlockLists': <{'easylist': 'https://v.firebog.net/hosts/Easylist.txt', 'hardlist': 'https://v.firebog.net/hosts/Hardlist.txt'}> }",
      "{ 'FilterType': <@u 1>, 'AllowLists': <{'easylist': 'https://v.firebog.net/hosts/Easylist.txt'}> }",
      "{ 'ForceSafeSearch': <true> }",
    };
  MctWebFilter *unequal_filters[G_N_ELEMENTS (unequal_filters_serialised)];

  /* Build a couple of filters which are identical. */
  equal_filters[0] = mct_web_filter_builder_end (&builder);

  mct_web_filter_builder_init (&builder);
  equal_filters[1] = mct_web_filter_builder_end (&builder);

  /* And a load of filters which are not. */
  for (size_t i = 0; i < G_N_ELEMENTS (unequal_filters_serialised); i++)
    {
      g_autoptr(GVariant) serialized = NULL;

      serialized = g_variant_parse (NULL, unequal_filters_serialised[i], NULL, NULL, NULL);
      g_assert (serialized != NULL);

      unequal_filters[i] = mct_web_filter_deserialize (serialized, 1, NULL);
      g_assert (unequal_filters[i] != NULL);
    }

  /* Test the equality checks on them all. */
  for (size_t i = 0; i < G_N_ELEMENTS (equal_filters); i++)
    for (size_t j = 0; j < G_N_ELEMENTS (equal_filters); j++)
      g_assert_true (mct_web_filter_equal (equal_filters[i], equal_filters[j]));

  for (size_t i = 0; i < G_N_ELEMENTS (unequal_filters); i++)
    {
      for (size_t j = 0; j < G_N_ELEMENTS (equal_filters); j++)
        g_assert_false (mct_web_filter_equal (unequal_filters[i], equal_filters[j]));
      for (size_t j = 0; j < G_N_ELEMENTS (unequal_filters); j++)
        {
          if (i != j)
            g_assert_false (mct_web_filter_equal (unequal_filters[i], unequal_filters[j]));
          else
            g_assert_true (mct_web_filter_equal (unequal_filters[i], unequal_filters[j]));
        }
    }

  for (size_t i = 0; i < G_N_ELEMENTS (equal_filters); i++)
    mct_web_filter_unref (equal_filters[i]);
  for (size_t i = 0; i < G_N_ELEMENTS (unequal_filters); i++)
    mct_web_filter_unref (unequal_filters[i]);
}

static void
test_web_filter_validate_filter_id (void)
{
  const char *valid_ids[] =
    {
      "a",
      "hi",
    };
  const char *invalid_ids[] =
    {
      "",
    };

  for (size_t i = 0; i < G_N_ELEMENTS (valid_ids); i++)
    g_assert_true (mct_web_filter_validate_filter_id (valid_ids[i]));

  for (size_t i = 0; i < G_N_ELEMENTS (invalid_ids); i++)
    g_assert_false (mct_web_filter_validate_filter_id (invalid_ids[i]));
}

static void
test_web_filter_validate_hostname (void)
{
  const char *valid_hostnames[] =
    {
      "a",
      "reddit.com",
      "www.rfc-editor.org",
      "reddit.com.",
    };
  const char *invalid_hostnames[] =
    {
      "",
      "123456781234567812345678123456781234567812345678123456781234567."
      "123456781234567812345678123456781234567812345678123456781234567."
      "123456781234567812345678123456781234567812345678123456781234567."
      "12345678123456781234567812345678123456781234567812345678123456",
      "1234567812345678123456781234567812345678123456781234567812345678.labeltoolong",
      "@",
      "@.invalidcharacter",
      "-.hyphenonlylabel",
      "-startswithhyphen",
      "endswithhyphen-",
      "underscores_not_allowed.some-site.nl",
      ".leadingdotnotallowed",
      "hyphenbeforedot-.com",
      ".",
      ".-",
      "reddit.com.-",
      "-",
      "finallabeltoolong.1234567812345678123456781234567812345678123456781234567812345678",
      "finallabeltoolong.1234567812345678123456781234567812345678123456781234567812345678.",
      "reddit.com..",
      "..",
    };

  for (size_t i = 0; i < G_N_ELEMENTS (valid_hostnames); i++)
    {
      g_assert_true (mct_web_filter_validate_hostname (valid_hostnames[i]));

      /* All valid hostnames should be valid domain names by definition */
      g_assert_true (mct_web_filter_validate_domain_name (valid_hostnames[i]));
    }

  for (size_t i = 0; i < G_N_ELEMENTS (invalid_hostnames); i++)
    g_assert_false (mct_web_filter_validate_hostname (invalid_hostnames[i]));
}

static void
test_web_filter_validate_domain_name (void)
{
  const char *valid_domain_names[] =
    {
      "a",
      "reddit.com",
      "www.rfc-editor.org",
      "10000_gratis_fotos.some-site.nl",
    };
  const char *invalid_domain_names[] =
    {
      "",
      "123456781234567812345678123456781234567812345678123456781234567."
      "123456781234567812345678123456781234567812345678123456781234567."
      "123456781234567812345678123456781234567812345678123456781234567."
      "12345678123456781234567812345678123456781234567812345678123456",
      "1234567812345678123456781234567812345678123456781234567812345678.labeltoolong",
      "finallabeltoolong.1234567812345678123456781234567812345678123456781234567812345678",
      "finallabeltoolong.1234567812345678123456781234567812345678123456781234567812345678.",
      "\0",
      "øh nø",
      "reddit.com..",
      ".",
      "..",
    };

  for (size_t i = 0; i < G_N_ELEMENTS (valid_domain_names); i++)
    g_assert_true (mct_web_filter_validate_domain_name (valid_domain_names[i]));

  for (size_t i = 0; i < G_N_ELEMENTS (invalid_domain_names); i++)
    g_assert_false (mct_web_filter_validate_domain_name (invalid_domain_names[i]));
}

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

  g_test_add_func ("/web-filter/types", test_web_filter_types);
  g_test_add_func ("/web-filter/refs", test_web_filter_refs);

  g_test_add_func ("/web-filter/serialize", test_web_filter_serialize);
  g_test_add_func ("/web-filter/deserialize", test_web_filter_deserialize);
  g_test_add_func ("/web-filter/deserialize/invalid", test_web_filter_deserialize_invalid);

  g_test_add_func ("/web-filter/equal", test_web_filter_equal);

  g_test_add_func ("/web-filter/validate-filter-id", test_web_filter_validate_filter_id);
  g_test_add_func ("/web-filter/validate-hostname", test_web_filter_validate_hostname);
  g_test_add_func ("/web-filter/validate-domain-name", test_web_filter_validate_domain_name);

  return g_test_run ();
}
