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

/*
 * - fakehome
 * - write config with seed and wrong perms
 * - verify error
 * - correct perms
 * - verify config loaded successfully
 * - TODO send/recv test (FAIL, wrong key)
 * - send/recv test (PASS, right key)
 * - TODO send/recv test (stdin)
 */

#include "test.h"
#include "testnet.h"
#include <agent.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/stat.h>
#include <unistd.h>

#define SPEEDLIMIT "1048576"
#define CHANNEL "0000-0030"
#define CMDLINE_PAYLOAD "Alonso"
#define WAITS 8
#define REDIRECT_BUF f = freopen("/dev/null", "a", stdout); assert(f); setbuf(stdout, buf);
#define REDIRECT_OUT f = freopen("/dev/tty", "a", stdout); assert(f); setbuf(stdout, NULL);

static char ifname[IFNAMSIZ];
static unsigned int ifx;
static char configfile[] = "0000-0030-XXXXXX/.lcagentrc";
static char payload[BUFSIZ];
static sem_t sem_recv;

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

	memset(buf, 0, sizeof buf);
	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;

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

	/* check payload */
	fprintf(stderr, "%s() got %zu bytes\n", __func__, strlen(buf));
	test_assert(!memcmp(payload, buf, sizeof buf), "recv buffer matches");
skip_test:
	sem_post(&sem_recv); /* tell main thread we're done */

	return arg;
}

static int test_payload_cmdline(state_t *state)
{
	char *argv[] = { PACKAGE_NAME, "send", "-v", "-i", ifname, "--loopback", "--bpslimit", SPEEDLIMIT, CHANNEL, CMDLINE_PAYLOAD, NULL };
	int argc = sizeof argv / sizeof argv[0] - 1;
	int rc;
	sprintf(payload, CMDLINE_PAYLOAD);
	rc = agent(state, argc, argv);
	return test_assert(rc == EXIT_SUCCESS, "agent() returned %i", rc);
}

static int test_payload(void)
{
	state_t state = {0};
	int rc;

	/* start recv thread */
	struct timespec timeout;
	pthread_t tid;
	rc = sem_init(&sem_recv, 0, 0);
	if (!test_assert(rc == 0, "sem_init()")) return test_status;
	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_payload_cmdline(&state);

	rc = clock_gettime(CLOCK_REALTIME, &timeout);
	if (!test_assert(rc == 0, "clock_gettime()")) goto err_sem_recv_destroy;
	timeout.tv_sec += 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);

	return test_status;
}


static int test_config(void)
{
	state_t state = {0};
	char channel_name[] = CHANNEL;
	char *argv[] = { PACKAGE_NAME, "send", "-v", "--loopback", "-i", ifname, channel_name, NULL };
	int argc = sizeof argv / sizeof argv[0] - 1;
	int rc;

	/* require that rcfile is not world readable */
	rc = agent(&state, argc, argv);
	test_assert(rc == EXIT_FAILURE, "%s() lcagent send returned %i", __func__, rc);

	/* fix permissions on rcfile */
	rc = chmod(configfile, S_IRUSR|S_IWUSR);
	if (!test_assert(rc == 0, "fix permissions on rcfile")) return test_status;
	rc = agent(&state, argc, argv);
	test_assert(rc == EXIT_SUCCESS, "%s() lcagent send returned %i", __func__, rc);

	return test_status;
}

static int test_write_config(void)
{
	char *home = getenv("HOME");
	FILE *f;
	int rc;

	rc = snprintf(configfile, sizeof configfile, "%s/.lcagentrc", home);
	if (!test_assert(rc == sizeof configfile - 1, "build configfile string")) return test_status;

	f = fopen(configfile, "w+");
	if (!test_assert(f != NULL, "creating config %s", configfile)) return test_status;

	/* ensure file is world-readable (insecure) */
	mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH;
	fchmod(fileno(f), mode);

	fprintf(f, "logfile lastlog.log\n");
	fprintf(f, "debug\n");
	fprintf(f, "seed password\n");
	fclose(f);

	return test_status;
}

static int create_keys_and_tokens(void)
{
	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);
	}
	{
		/* 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;
}

static int create_fake_home(void)
{
	char fakehome[] = "0000-0030-XXXXXX";
	if (!test_assert(mkdtemp(fakehome) != NULL, "mkdtemp()")) {
		perror("mkdtemp");
		return test_status;
	}
	setenv("HOME", fakehome, 1);
	return test_status;
}

int main(void)
{
	char name[] = "symmetric encryption";

	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;

	if (create_fake_home()) return test_status;
	if (test_write_config()) return test_status;
	if (test_config()) return test_status;
	if (create_keys_and_tokens()) return test_status;
	if (test_payload()) return test_status;

	return test_status;
}
