/* -*- 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 <libmalcontent-web/filter-list.h>
#include <locale.h>


static gboolean
parse_cb (const char  *hostname,
          size_t       hostname_len,
          void        *user_data,
          GError     **error)
{
  GList **hostnames_out = user_data;

  *hostnames_out = g_list_prepend (*hostnames_out, g_strndup (hostname, hostname_len));

  return TRUE;
}

static void
test_filter_list_parsing (void)
{
  const struct
    {
      const char *data;
      size_t data_len;  /* might not just be strlen(data) if we want to test length handling */
      gboolean expected_success;
      const char *expected_hostnames[10];  /* (array zero-terminated=1) */
    }
  vectors[] =
    {
      { "", 0, TRUE, { NULL, } },
      { "    ", 0, TRUE, { NULL, } },
      { "# just a comment", 0, TRUE, { NULL, } },
      { "# just a comment\n", 0, TRUE, { NULL, } },
      { "   # just a comment", 0, TRUE, { NULL, } },
      { "example.com", 0, TRUE, { "example.com", NULL, } },
      { "example.com\n", 0, TRUE, { "example.com", NULL, } },
      { "example.com\n\n\n", 0, TRUE, { "example.com", NULL, } },
      { "   example.com\n", 0, TRUE, { "example.com", NULL, } },
      { "   example.com   \n", 0, TRUE, { "example.com", NULL, } },
      { "example.com   \n   ", 0, TRUE, { "example.com", NULL, } },
      { "example.com\ntest.com", 0, TRUE, { "example.com", "test.com", NULL, } },
      { "example.com\n# one comment\n  # another comment\ntest.com", 0, TRUE, { "example.com", "test.com", NULL, } },
    };

  for (size_t i = 0; i < G_N_ELEMENTS (vectors); i++)
    {
      gboolean success;
      g_autoptr(GError) local_error = NULL;
      GList *hostnames = NULL;

      success = mct_filter_list_parse_from_data (vectors[i].data,
                                                 (vectors[i].data_len != 0) ? vectors[i].data_len : strlen (vectors[i].data),
                                                 parse_cb,
                                                 &hostnames,
                                                 &local_error);
      hostnames = g_list_reverse (hostnames);  /* because we use g_list_prepend() above */

      if (vectors[i].expected_success)
        {
          size_t j = 0;

          g_assert_true (success);
          g_assert_no_error (local_error);

          g_assert_cmpuint (g_list_length (hostnames), ==, g_strv_length ((char **) vectors[i].expected_hostnames));

          for (GList *l = hostnames; l != NULL; l = l->next, j++)
            {
              const char *expected_hostname = vectors[i].expected_hostnames[j];
              const char *hostname = (const char *) l->data;

              g_assert_cmpstr (hostname, ==, expected_hostname);
            }
        }
      else
        {
          /* We don’t need to check the exact error which has been set, because
           * it’s only set in parse_cb(), which is part of the test harness. */
          g_assert_false (success);
          g_assert_nonnull (local_error);
          g_assert_null (hostnames);
        }

      g_list_free_full (hostnames, g_free);
    }
}

static gboolean
parse_error_cb (const char  *hostname,
                size_t       hostname_len,
                void        *user_data,
                GError     **error)
{
  g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "oh no");

  return FALSE;
}

static void
test_filter_list_parsing_reject_hostname (void)
{
  gboolean success;
  g_autoptr(GError) local_error = NULL;

  success = mct_filter_list_parse_from_data ("example.com",
                                             strlen ("example.com"),
                                             parse_error_cb,
                                             NULL,
                                             &local_error);

  /* We don’t need to check the exact error which has been set, because
   * it’s only set in parse_cb(), which is part of the test harness. */
  g_assert_false (success);
  g_assert_nonnull (local_error);
}

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

  g_test_add_func ("/filter-list/parsing", test_filter_list_parsing);
  g_test_add_func ("/filter-list/parsing/reject-hostname", test_filter_list_parsing_reject_hostname);

  return g_test_run ();
}
