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

#include "test.h"
#include "testdata.h"
#include "testnet.h"
#include "testsync.h"
#include <errno.h>
#include <librecast_pvt.h>
#include <librecast/mdex.h>
#include <librecast/mtree.h>
#include <librecast/net.h>
#include <librecast/sync.h>
#include <unistd.h>

#if HAVE_RQ_OTI
#define MAXFILESZ 1048576
#define MAXFILES 10
#define MAXDIRS 5
#define DEPTH 2
#define TIMEOUT_SECONDS 10

/*
 * test 0121
 *
 * create a source directory with a large number of subdirectories such that
 * the tree does not fit in a single datagram.
 * Sync recursively to destination directory.
 *
 * Compare the resulting directories and ensure they match.
 */

static sem_t sem_recv;

struct pkg_s {
	char *src;
	char *dst;
};

static int create_subdirs(const char *src, size_t n)
{
	char *dir;
	size_t len;
	int rc = 0;
	len = strlen(src) + 8;
	dir = malloc(len);
	if (!dir) return -1;
	test_log("creating %zu subdirectories\n", n);
	while (n--) {
		snprintf(dir, len, "%s/%zu", src, n);
		rc = mkdir(dir, 0755);
		if (rc != 0) break;
	}
	free(dir);
	return rc;
}

void *thread_recv(void *arg)
{
	struct pkg_s *pkg = (struct pkg_s *)arg;
	unsigned char hash[HASHSIZE];
	const int flags =
		SYNC_RECURSE | SYNC_ATIME | SYNC_MTIME | SYNC_OWNER | SYNC_GROUP | SYNC_MODE;
	ssize_t rc;
	mdex_aliashash(pkg->src, hash, sizeof hash);
	lc_ctx_t *lctx = lc_ctx_new();
	pthread_cleanup_push((void (*)(void *))lc_ctx_free, lctx);
	rc = lc_syncfile(lctx, hash, pkg->dst, NULL, NULL, NULL, flags);
	test_assert(rc > 0, "lc_syncfile returned %zi", rc);
	pthread_cleanup_pop(1); /* lc_ctx_free */
	sem_post(&sem_recv);
	return NULL;
}
#endif /* HAVE_RQ_OTI */

int main(int argc, char *argv[])
{
	(void)argc, (void)argv;
	char name[] = "lc_syncfile() - recursive file syncing (large directory)";
#if HAVE_RQ_OTI
	lc_ctx_t *lctx = NULL;
	mdex_t *mdex;
	lc_share_t *share = NULL;
	pthread_t tid_recv;
	struct pkg_s pkg_recv = {0};
	struct timespec timeout = {0};
	char *src = NULL, *dst = NULL;
	int rc;

	test_cap_require(CAP_NET_ADMIN);
	test_name(name);
	test_require_net(TEST_NET_BASIC);

	/* create src and dst directories */
	rc = test_createtestdirs(basename(argv[0]), &src, &dst);
	if (!test_assert(rc == 0, "test_createtestdirs()")) return test_status;

	/* create subdirectories in src */
	/* we can fit 32 x 32byte hashes in a 1024 byte payload. Create a few
	 * more entries than that */
	size_t n = 257;
	create_subdirs(src, n);

	/* index source tree */
	mdex = mdex_init(512);
	if (!test_assert(mdex != NULL, "mdex_init()")) goto err_free_src_dst;
	rc = mdex_addfile(mdex, src, NULL, MDEX_RECURSE);
	if (!test_assert(rc == 0, "mdex_addfile() returned %i", rc)) goto err_mdex_free;

#if 0
	/* XXX; match src and dst match */
	char cmd[128];
	//snprintf(cmd, sizeof cmd, "rsync -avq %s/ %s", src, dst);
	snprintf(cmd, sizeof cmd, "cp -Rp %s/. %s", src, dst);
	test_log("`%s`\n", cmd);
	if (system(cmd)) { test_assert(0, "cmd failed"); goto err_free_src_dst; }
#endif

	/* share the mdex */
	lctx = lc_ctx_new();
	if (!test_assert(lctx != NULL, "lc_ctx_new()")) goto err_mdex_free;
	share = lc_share(lctx, mdex, 0, NULL, NULL, LC_SHARE_LOOPBACK);
	if (!test_assert(share != NULL, "lc_share()")) goto err_free_lctx;
	test_assert(share != NULL, "lc_share()");

	/* start receive thread, sync files */
	test_log("syncing '%s' => '%s'\n", src, dst);
	rc = sem_init(&sem_recv, 0, 0);
	if (!test_assert(rc == 0, "sem_init(sem_recv)")) goto err_unshare;
	pkg_recv.src = src;
	pkg_recv.dst = dst;
	rc = pthread_create(&tid_recv, NULL, thread_recv, &pkg_recv);
	if (!test_assert(rc == 0, "create recv thread")) goto err_sem_destroy;

	/* handle timeout */
	clock_gettime(CLOCK_REALTIME, &timeout);
	timeout.tv_sec += TIMEOUT_SECONDS;
	if ((rc = sem_timedwait(&sem_recv, &timeout)) == -1 && errno == ETIMEDOUT) {
		pthread_cancel(tid_recv);
	}
	test_assert(rc == 0, "timeout waiting for recv thread");
	pthread_join(tid_recv, NULL);

	/* verify src and dst match */
	test_verify_dirs(src, dst);
err_sem_destroy:
	sem_destroy(&sem_recv);
err_unshare:
	lc_unshare(share);
err_free_lctx:
	lc_ctx_free(lctx);
err_mdex_free:
	mdex_free(mdex);
err_free_src_dst:
	free(src); free(dst);
	return test_status;
#else
	return test_skip(name);
#endif /* HAVE_RQ_OTI */
}
