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

#include "test.h"
#include "testnet.h"
#include <agent.h>
#include <pthread.h>
#include <semaphore.h>

#define CHANNEL_NAME "transgender rights are human rights"
#define WAITS 4
#define SPEEDLIMIT "1048576"
#define REDIRECT_BUF f = freopen("/dev/null", "a", stderr); assert(f); setbuf(stderr, buf);
#define REDIRECT_OUT setbuf(stderr, NULL); f = freopen("lastlog.log", "a", stderr);

static sem_t sem_recv;
static unsigned int ifx;
static char ifname[IFNAMSIZ];

void *thread_recv(void *arg)
{
	state_t state = {0};
	char *argv_00[] = { PACKAGE_NAME, "-v", "-i", ifname, "recv", CHANNEL_NAME, NULL };
	int argc = sizeof argv_00 / sizeof argv_00[0] - 1;
	int rc;

	rc = agent_load_config(&state, argc, argv_00, NULL);
	sem_post(&sem_recv); /* tell main thread we're ready to recv */
	if (!test_assert(rc == 0, "agent_load_config()")) goto skip_test;

	rc = agent_run(&state);
	free_state(&state);
	test_assert(rc == EXIT_SUCCESS, "agent_run() returned %i", rc);

skip_test:
	sem_post(&sem_recv); /* tell main thread we're done */

	return arg;
}

static int create_keys_and_tokens(int make_token)
{
	state_t state = {0};
	int rc;
	{
		/* generate keys */
		char *argv[] = { PACKAGE_NAME, "whoami", NULL };
		int argc = sizeof argv / sizeof argv[0] - 1;
		rc = agent(&state, argc, argv);
		test_assert(rc == EXIT_SUCCESS, "agent() returned %i", rc);
	}
	if (make_token) {
		/* generate token */
		char *bearer_key = state.defaults.keyring.phex;
		char *argv[] = { PACKAGE_NAME, "sign", bearer_key, CHANNEL_NAME, NULL };
		int argc = sizeof argv / sizeof argv[0] - 1;
		rc = agent(&state, argc, argv);
		test_assert(rc == EXIT_SUCCESS, "agent() returned %i", rc);
	}
	{
		/* add trusted key (self-signed) */
		char *signer_key = state.defaults.keyring.phex;
		char *argv[] = { PACKAGE_NAME, "key", "add", signer_key, NULL };
		int argc = sizeof argv / sizeof argv[0] - 1;
		rc = agent(&state, argc, argv);
		test_assert(rc == EXIT_SUCCESS, "agent() returned %i", rc);
	}
	return test_status;
}

int test_token(int presign_token)
{
	char fakehome[] = "0000-0018-XXXXXX";
	char buf[BUFSIZ];
	state_t state = {0};
	FILE *f;
	int argc;
	int rc;

	/* create fake home directory */
	if (!test_assert(mkdtemp(fakehome) != NULL, "mkdtemp()")) {
		perror("mkdtemp");
		return test_status;
	}
	setenv("HOME", fakehome, 1);

	/* generate keys */
	if (create_keys_and_tokens(presign_token)) return test_status;

	/* missing required arguments  - missing channel */
	test_log("%s() missing requred arguments - missing channel\n", __func__);
	char *argv_00[] = { PACKAGE_NAME, "send", NULL };
	argc = sizeof argv_00 / sizeof argv_00[0] - 1;
	memset(buf, 0, sizeof buf); /* clear output buffer */
	REDIRECT_BUF
	rc = agent(&state, argc, argv_00);
	REDIRECT_OUT
	test_assert(errno == EINVAL, "EINVAL");
	test_assert(rc == EXIT_FAILURE, "agent() returned %i", rc);
	/* verify cmd output (error message) */
	test_assert(strstr(buf, AGENT_ERR_NOCHANNEL) != NULL, "error message found in output");

	/* start recv thread */
	struct timespec timeout;
	pthread_t tid;
	rc = sem_init(&sem_recv, 0, 0);
	if (!test_assert(rc == 0, "sem_init()")) goto err_close_redirect;
	rc = pthread_create(&tid, NULL, &thread_recv, NULL);
	if (!test_assert(rc == 0, "pthread_create() recv thread")) goto err_sem_recv_destroy;

	sem_wait(&sem_recv); /* wait until recv thread ready */

	test_log("%s() send with valid token and channel\n", __func__);
	char *argv_01[] = { PACKAGE_NAME, "send", "-v", "-i", ifname, "--loopback", "--bpslimit", SPEEDLIMIT, CHANNEL_NAME, NULL };
	argc = sizeof argv_01 / sizeof argv_01[0] - 1;
	rc = agent(&state, argc, argv_01);
	test_assert(rc == EXIT_SUCCESS, "agent() returned %i", rc);

	rc = clock_gettime(CLOCK_REALTIME, &timeout);
	if (!test_assert(rc == 0, "clock_gettime()")) goto err_sem_recv_destroy;
	timeout.tv_sec += ((RUNNING_ON_VALGRIND) ? WAITS * 2 : WAITS);
	rc = sem_timedwait(&sem_recv, &timeout);
	test_assert(rc == 0, "timeout");
	pthread_cancel(tid);
	pthread_join(tid, NULL);
	if (rc != 0) goto err_sem_recv_destroy;

err_sem_recv_destroy:
	sem_destroy(&sem_recv);
err_close_redirect:

	return test_status;
}

int main(void)
{
	char name[] = PACKAGE_NAME " send + recv";

	test_name(name);
	test_require_net(TEST_NET_BASIC);

	ifx = get_multicast_if();
	if (!ifx) return (test_status = TEST_WARN);
	if (!test_assert(if_indextoname(ifx, ifname) != NULL, "if_indextoname()"))
		return test_status;

	test_assert(test_token(1) == 0, "test with pre-signed token");
	test_assert(test_token(0) == 0, "test with self-signed token");

	return test_status;
}
