/* 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 4
#define MAXDIRS 2
#define DEPTH 2
#define TIMEOUT_SECONDS 100

/*
 * test 0117
 *
 * create a tree of files and directories and use lc_syncfile() to 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;
};

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";
#if HAVE_RQ_OTI
	lc_ctx_t *lctx;
	mdex_t *mdex;
	lc_share_t *share;
	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 source directory tree and files */
	rc = test_createtestdirs(basename(argv[0]), &src, &dst);
	if (!test_assert(rc == 0, "test_createtestdirs()")) return test_status;
	test_random_meta(src, TEST_OWN|TEST_MOD);
	rc = test_createtesttree(src, MAXFILESZ, MAXFILES, MAXDIRS, DEPTH, TEST_OWN|TEST_MOD);
	if (!test_assert(rc == 0, "test_createtesttree()")) goto err_free_src_dst;

	/* 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 */
}
