// SPDX-License-Identifier: GPL-3.0-or-later

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "hashmap.h"
#include "util.h"

#define TEST(name) static void name(void)

#define RUN(name)				\
  do {						\
    printf("  %-40s", #name "...");		\
    name();					\
    printf(" OK\n");				\
  } while (0)

TEST(test_new_free) {
  OrderedHashmap *h = ordered_hashmap_new();
  assert(h != NULL);
  assert(ordered_hashmap_size(h) == 0);
  ordered_hashmap_free(h);
}

TEST(test_free_null) { ordered_hashmap_free(NULL); }

TEST(test_put_get) {
  _cleanup_hashmap_free_ OrderedHashmap *h = ordered_hashmap_new();

  int val1 = 1, val2 = 2, val3 = 3;

  assert(ordered_hashmap_put(h, "alpha", &val1) == 0);
  assert(ordered_hashmap_put(h, "beta", &val2) == 0);
  assert(ordered_hashmap_put(h, "gamma", &val3) == 0);
  assert(ordered_hashmap_size(h) == 3);

  assert(ordered_hashmap_get(h, "alpha") == &val1);
  assert(ordered_hashmap_get(h, "beta") == &val2);
  assert(ordered_hashmap_get(h, "gamma") == &val3);
}

TEST(test_get_missing) {
  _cleanup_hashmap_free_ OrderedHashmap *h = ordered_hashmap_new();
  assert(ordered_hashmap_get(h, "missing") == NULL);
}

TEST(test_contains) {
  _cleanup_hashmap_free_ OrderedHashmap *h = ordered_hashmap_new();
  int val = 42;

  assert(!ordered_hashmap_contains(h, "key"));
  assert(ordered_hashmap_put(h, "key", &val) == 0);
  assert(ordered_hashmap_contains(h, "key"));
  assert(!ordered_hashmap_contains(h, "other"));
}

TEST(test_duplicate_key) {
  _cleanup_hashmap_free_ OrderedHashmap *h = ordered_hashmap_new();
  int val1 = 1, val2 = 2;

  assert(ordered_hashmap_put(h, "key", &val1) == 0);
  // putting the same key again should fail
  int r = ordered_hashmap_put(h, "key", &val2);
  assert(r == -EEXIST);
  assert(ordered_hashmap_size(h) == 1);
  // original value should be preserved
  assert(ordered_hashmap_get(h, "key") == &val1);
}

TEST(test_remove) {
  _cleanup_hashmap_free_ OrderedHashmap *h = ordered_hashmap_new();
  int val1 = 1, val2 = 2, val3 = 3;

  ordered_hashmap_put(h, "a", &val1);
  ordered_hashmap_put(h, "b", &val2);
  ordered_hashmap_put(h, "c", &val3);

  void *removed = ordered_hashmap_remove(h, "b");
  assert(removed == &val2);
  assert(ordered_hashmap_size(h) == 2);
  assert(ordered_hashmap_get(h, "b") == NULL);
  assert(!ordered_hashmap_contains(h, "b"));

  // a and c should still be there
  assert(ordered_hashmap_get(h, "a") == &val1);
  assert(ordered_hashmap_get(h, "c") == &val3);
}

TEST(test_remove_missing) {
  _cleanup_hashmap_free_ OrderedHashmap *h = ordered_hashmap_new();
  assert(ordered_hashmap_remove(h, "nothing") == NULL);
}

TEST(test_iteration_order) {
  _cleanup_hashmap_free_ OrderedHashmap *h = ordered_hashmap_new();
  int vals[] = {10, 20, 30, 40, 50};
  const char *keys[] = {"first", "second", "third", "fourth", "fifth"};

  for (size_t i = 0; i < 5; i++)
    ordered_hashmap_put(h, keys[i], &vals[i]);

  // iteration should follow insertion order
  const char *k;
  void *v;
  size_t idx = 0;
  ORDERED_HASHMAP_FOREACH(k, v, h) {
    assert(streq(k, keys[idx]));
    assert(*(int *)v == vals[idx]);
    idx++;
  }
  assert(idx == 5);
}

TEST(test_iteration_after_remove) {
  _cleanup_hashmap_free_ OrderedHashmap *h = ordered_hashmap_new();
  int v1 = 1, v2 = 2, v3 = 3;

  ordered_hashmap_put(h, "x", &v1);
  ordered_hashmap_put(h, "y", &v2);
  ordered_hashmap_put(h, "z", &v3);

  ordered_hashmap_remove(h, "y");

  const char *k;
  void *v;
  size_t count = 0;
  ORDERED_HASHMAP_FOREACH(k, v, h) {
    if (count == 0) {
      assert(streq(k, "x"));
      assert(*(int *)v == 1);
    } else if (count == 1) {
      assert(streq(k, "z"));
      assert(*(int *)v == 3);
    }
    count++;
  }
  assert(count == 2);
}

TEST(test_many_entries) {
  _cleanup_hashmap_free_ OrderedHashmap *h = ordered_hashmap_new();

  // insert 200 entries to exercise resizing
  char keybuf[200][32];
  int vals[200];
  for (int i = 0; i < 200; i++) {
    snprintf(keybuf[i], sizeof(keybuf[i]), "key_%03d", i);
    vals[i] = i * 7;
    assert(ordered_hashmap_put(h, keybuf[i], &vals[i]) == 0);
  }

  assert(ordered_hashmap_size(h) == 200);

  // verify all can be looked up
  for (int i = 0; i < 200; i++) {
    void *v = ordered_hashmap_get(h, keybuf[i]);
    assert(v != NULL);
    assert(*(int *)v == i * 7);
  }

  // verify iteration order
  const char *k;
  void *v;
  int idx = 0;
  ORDERED_HASHMAP_FOREACH(k, v, h) {
    assert(streq(k, keybuf[idx]));
    assert(*(int *)v == idx * 7);
    idx++;
  }
  assert(idx == 200);
}

TEST(test_size_null) { assert(ordered_hashmap_size(NULL) == 0); }

TEST(test_path_keys) {
  // test with typical seedfiles path keys
  _cleanup_hashmap_free_ OrderedHashmap *h = ordered_hashmap_new();
  int vals[6];

  const char *paths[] = {"/tmp",      "/var/tmp",
                         "/run/lock", "/etc/os-release",
                         "/dev/net",  "/var/cache/man"};

  for (size_t i = 0; i < 6; i++) {
    vals[i] = (int)i;
    assert(ordered_hashmap_put(h, paths[i], &vals[i]) == 0);
  }

  assert(ordered_hashmap_size(h) == 6);
  for (size_t i = 0; i < 6; i++)
    assert(ordered_hashmap_get(h, paths[i]) == &vals[i]);

  // prefix search (simulating link_parent)
  assert(ordered_hashmap_get(h, "/tmp") != NULL);
  assert(ordered_hashmap_get(h, "/var") == NULL);
  assert(ordered_hashmap_get(h, "/var/tmp") != NULL);
}

int main(void) {
  printf("test-hashmap:\n");
  RUN(test_new_free);
  RUN(test_free_null);
  RUN(test_put_get);
  RUN(test_get_missing);
  RUN(test_contains);
  RUN(test_duplicate_key);
  RUN(test_remove);
  RUN(test_remove_missing);
  RUN(test_iteration_order);
  RUN(test_iteration_after_remove);
  RUN(test_many_entries);
  RUN(test_size_null);
  RUN(test_path_keys);
  printf("All tests passed.\n");
  return EXIT_SUCCESS;
}
