/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only */
/* Copyright (c) 2023 Brett Sheffield <bacs@librecast.net> */

/* test 0120
 *
 * - create src and dst directories
 * - create/modify files (including metadata)
 * - test directory hashes change when expected
 */

#include "test.h"
#include "testdata.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <librecast/mdex.h>
#include <librecast/sync.h>
#include <unistd.h>
#include <utime.h>
#include <sys/time.h>

#define MAXFILESZ 1048576
#define MAXFILES 10
#define MAXDIRS 5
#define DEPTH 3

static int test_hash_nomatch(unsigned char *hash1, unsigned char *hash2, size_t hashlen)
{
	hash_hex_debug(stderr, hash1, hashlen);
	hash_hex_debug(stderr, hash2, hashlen);
	return test_assert(memcmp(hash1, hash2, hashlen), "hashes MUST NOT match");
}

static int test_hash_match(unsigned char *hash1, unsigned char *hash2, size_t hashlen)
{
	hash_hex_debug(stderr, hash1, hashlen);
	hash_hex_debug(stderr, hash2, hashlen);
	return test_assert(!memcmp(hash1, hash2, hashlen), "hashes MUST match");
}

static int hash_dir_nomatch(const char *dir1, const char *dir2)
{
	unsigned char root1[HASHSIZE] = "";
	unsigned char root2[HASHSIZE] = "";
	int rc;

	test_log("calculate root hash of directories, ensure match\n");
	rc = mdex_get_directory_root(dir1, root1);
	if (!test_assert(rc == 0, "mdex_get_directory_root() '%s'", dir1)) return -1;

	rc = mdex_get_directory_root(dir2, root2);
	if (!test_assert(rc == 0, "mdex_get_directory_root() '%s'", dir2)) return -1;

	return test_hash_nomatch(root1, root2, HASHSIZE);
}

static int hash_dir_match(const char *dir1, const char *dir2)
{
	unsigned char root1[HASHSIZE] = "";
	unsigned char root2[HASHSIZE] = "";
	int rc;

	test_log("calculate root hash of directories, ensure match\n");
	rc = mdex_get_directory_root(dir1, root1);
	if (!test_assert(rc == 0, "mdex_get_directory_root() '%s'", dir1)) return -1;

	rc = mdex_get_directory_root(dir2, root2);
	if (!test_assert(rc == 0, "mdex_get_directory_root() '%s'", dir2)) return -1;

	return test_hash_match(root1, root2, HASHSIZE);
}

static int mkfileat(const char *dir, const char *filename, size_t sz, const struct timeval * when)
{
	char owd[PATH_MAX];
	int rc;
	if (!test_assert(getcwd(owd, sizeof owd) != NULL, "getcwd()")) return -1;
	rc = chdir(dir);
	if (!test_assert(rc == 0, "chdir() %s", dir)) return -1;
	rc = creat(filename, 0644);
	if (!test_assert(rc != -1, "creat() %s", filename)) return -1;
	close(rc);
	if (sz) test_file_scratch(filename, 0, sz);
	if (when) utimes(filename, when);
	return chdir(owd);
}

/* cd to dir and create directory called dirname with mode */
static int mkdirin(const char *dir, const char *dirname, mode_t mode, const struct timeval * later)
{
	char owd[PATH_MAX];
	int rc;
	if (!test_assert(getcwd(owd, sizeof owd) != NULL, "getcwd()")) return -1;
	rc = chdir(dir);
	if (!test_assert(rc == 0, "chdir() %s", dir)) return -1;
	rc = mkdir(dirname, mode);
	if (!test_assert(rc ==0, "mkdir() %s", dirname)) return -1;
	if (later) utimes(dirname, later);
	return chdir(owd);
}

static int syncmtime(const char *path1, const char *path2, const char *filename)
{
	char owd[PATH_MAX];
	struct timespec t[2] = {0};
	struct stat sb;
	int rc;

	if (!test_assert(getcwd(owd, sizeof owd) != NULL, "getcwd()")) return -1;

	rc = chdir(path1);
	if (!test_assert(rc == 0, "chdir() %s", path1)) return -1;
	if (stat(filename, &sb) == -1) return -1;

	rc = chdir(owd);
	if (!test_assert(rc == 0, "chdir() %s", owd)) return -1;

	rc = chdir(path2);
	if (!test_assert(rc == 0, "chdir() %s", path2)) return -1;
	t[1] = sb.st_mtim;
	rc = utimensat(AT_FDCWD, filename, t, AT_SYMLINK_NOFOLLOW);
	if (!test_assert(rc == 0, "utimensat()")) return -1;
	return chdir(owd);
}

int main(int argc, char *argv[])
{
	(void)argc;
	const char empty[] = "emptyfile";
	const char dirname[] = "newdir";
	char *src = NULL, *dst = NULL;
	struct timeval later[2];
	int rc;

	gettimeofday(later, NULL);
	later[0].tv_sec++;
	later[1] = later[0];
	test_name("much ado about directory hashes (part 2)");

	test_log("create source directory tree and files\n");
	rc = test_createtestdirs(basename(argv[0]), &src, &dst);
	if (!test_assert(rc == 0, "test_createtestdirs()")) return test_status;
	if (!hash_dir_match(src, dst)) goto err_free_src_dst;

	test_log("create new (empty) file in src directory\n");
	rc = mkfileat(src, empty, 0, NULL);
	if (!test_assert(rc == 0, "mkfileat() %s/%s", src, empty)) goto err_free_src_dst;
	if (!hash_dir_nomatch(src, dst)) goto err_free_src_dst;

	/* create a similar file in dst directory, but with different mtime */
	test_log("create new (empty) file in dst directory\n");
	rc = mkfileat(dst, empty, 0, later);
	if (!test_assert(rc == 0, "mkfileat() %s/%s", dst, empty)) goto err_free_src_dst;
	test_log("hashes MUST not match, because mtime does not match\n");
	if (!hash_dir_nomatch(src, dst)) goto err_free_src_dst;

	test_log("sync mtime, recheck\n");
	if (syncmtime(src, dst, empty)) goto err_free_src_dst;
	if (!hash_dir_match(src, dst)) goto err_free_src_dst;

	test_log("create a directory in src, ensure hashes don't match\n");
	if (mkdirin(src, dirname, 0755, NULL)) goto err_free_src_dst;
	if (!hash_dir_nomatch(src, dst)) goto err_free_src_dst;

	test_log("create same name directory in dst\n");
	if (mkdirin(dst, dirname, 0755, later)) goto err_free_src_dst;
	test_log("hashes MUST not match, because mtime does not match\n");
	if (!hash_dir_nomatch(src, dst)) goto err_free_src_dst;

	test_log("sync mtime, recheck (hashes MUST now match)\n");
	if (syncmtime(src, dst, dirname)) goto err_free_src_dst;
	if (!hash_dir_match(src, dst)) goto err_free_src_dst;


err_free_src_dst:
	free(src); free(dst);
	return test_status;
}
