/*  Copyright (C) CZ.NIC, z.s.p.o. and contributors
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  For more information, see <https://www.knot-dns.cz/>
 */

#define CONFIG_DIR	"/tmp"

#include <tap/basic.h>

#include "knot/conf/conf.c"
#include "test_conf.h"

#define ZONE_ARPA	"0/25.2.0.192.in-addr.arpa."
#define ZONE_ROOT	"."
#define ZONE_1LABEL	"x."
#define ZONE_3LABEL	"abc.ab.a."
#define ZONE_UNKNOWN	"unknown."

static void check_name(const char *zone, const char *name, const char *ref)
{
	knot_dname_t *z = knot_dname_from_str_alloc(zone);

	char *file = conf_get_filename_txn(conf(), NULL, z, name);
	ok(file != NULL, "Get zonefile path for %s", zone);
	if (file != NULL) {
		ok(strcmp(file, ref) == 0, "Zonefile path compare %s", name);
		free(file);
	}

	knot_dname_free(z, NULL);
}

static void check_name_err(const char *zone, const char *name)
{
	knot_dname_t *z = knot_dname_from_str_alloc(zone);

	char *filename = conf_get_filename_txn(conf(), NULL, z, name);
	ok(filename == NULL, "Invalid name %s", name);
	free(filename);

	knot_dname_free(z, NULL);
}

static void test_get_filename(void)
{
	int ret = test_conf("", NULL);
	is_int(KNOT_EOK, ret, "Prepare empty configuration");

	// Name formatter.
	char *zone = "abc";
	check_name(zone, "/%s", "/abc");

	zone = ".";
	check_name(zone, "/%s", "/");

	// Char formatter.
	zone = "abc.def.g";
	check_name(zone, "/%c[0]", "/a");
	check_name(zone, "/%c[3]", "/.");
	check_name(zone, "/%c[8]", "/g");
	check_name(zone, "/%c[9]", "/.");
	check_name(zone, "/%c[10]", "/");
	check_name(zone, "/%c[255]", "/");
	check_name(zone, "/%c[0-1]", "/ab");
	check_name(zone, "/%c[1-1]", "/b");
	check_name(zone, "/%c[1-3]", "/bc.");
	check_name(zone, "/%c[1-4]", "/bc.d");
	check_name(zone, "/%c[254-255]", "/");
	check_name_err(zone, "/%c");
	check_name_err(zone, "/%cx");
	check_name_err(zone, "/%c[a]");
	check_name_err(zone, "/%c[:]");
	check_name_err(zone, "/%c[/]");
	check_name_err(zone, "/%c[-1]");
	check_name_err(zone, "/%c[256]");
	check_name_err(zone, "/%c[");
	check_name_err(zone, "/%c[1");
	check_name_err(zone, "/%c[1-");
	check_name_err(zone, "/%c[1-2");
	check_name_err(zone, "/%c[1-b]");
	check_name_err(zone, "/%c[8-0]");

	zone = "abcd";
	check_name(zone, "/%c[2-9]", "/cd.");
	check_name(zone, "/%c[3]", "/d");
	check_name(zone, "/%c[4]", "/.");

	zone = ".";
	check_name(zone, "/%c[0]", "/.");
	check_name(zone, "/%c[1]", "/");

	// Label formatter.
	zone = "abc.def.gh";
	check_name(zone, "/%l[0]", "/gh");
	check_name(zone, "/%l[1]", "/def");
	check_name(zone, "/%l[2]", "/abc");
	check_name(zone, "/%l[3]", "/");
	check_name(zone, "/%l[255]", "/");
	check_name(zone, "/%l[0]-%l[1]-%l[2]", "/gh-def-abc");
	check_name_err(zone, "/%l[0-1]");
	check_name_err(zone, "/%l[-1]");
	check_name_err(zone, "/%l[256]");

	zone = ".";
	check_name(zone, "/%l[0]", "/");
	check_name(zone, "/%l[1]", "/");

	test_conf_free();
}

static void test_conf_zonefile(void)
{
	int ret;
	char *file;

	knot_dname_t *zone_arpa = knot_dname_from_str_alloc(ZONE_ARPA);
	ok(zone_arpa != NULL, "create dname "ZONE_ARPA);
	knot_dname_t *zone_root = knot_dname_from_str_alloc(ZONE_ROOT);
	ok(zone_root != NULL, "create dname "ZONE_ROOT);
	knot_dname_t *zone_1label = knot_dname_from_str_alloc(ZONE_1LABEL);
	ok(zone_1label != NULL, "create dname "ZONE_1LABEL);
	knot_dname_t *zone_3label = knot_dname_from_str_alloc(ZONE_3LABEL);
	ok(zone_3label != NULL, "create dname "ZONE_3LABEL);
	knot_dname_t *zone_unknown = knot_dname_from_str_alloc(ZONE_UNKNOWN);
	ok(zone_unknown != NULL, "create dname "ZONE_UNKNOWN);

	const char *conf_str =
		"template:\n"
		"  - id: default\n"
		"    storage: /tmp\n"
		"\n"
		"zone:\n"
		"  - domain: "ZONE_ARPA"\n"
		"    file: dir/a%%b/%s.suffix/%a\n"
		"  - domain: "ZONE_ROOT"\n"
		"    file: /%s\n"
		"  - domain: "ZONE_1LABEL"\n"
		"    file: /%s\n"
		"  - domain: "ZONE_3LABEL"\n";

	ret = test_conf(conf_str, NULL);
	is_int(KNOT_EOK, ret, "Prepare configuration");

	// Relative path with formatters.
	file = conf_zonefile(conf(), zone_arpa);
	ok(file != NULL, "Get zonefile path for "ZONE_ARPA);
	if (file != NULL) {
		ok(strcmp(file, "/tmp/dir/a%b/0_25.2.0.192.in-addr.arpa.suffix/") == 0,
		          "Zonefile path compare for "ZONE_ARPA);
		free(file);
	}

	// Absolute path without formatters - root zone.
	file = conf_zonefile(conf(), zone_root);
	ok(file != NULL, "Get zonefile path for "ZONE_ROOT);
	if (file != NULL) {
		ok(strcmp(file, "/") == 0,
		          "Zonefile path compare for "ZONE_ROOT);
		free(file);
	}

	// Absolute path without formatters - non-root zone.
	file = conf_zonefile(conf(), zone_1label);
	ok(file != NULL, "Get zonefile path for "ZONE_1LABEL);
	if (file != NULL) {
		ok(strcmp(file, "/x") == 0,
		          "Zonefile path compare for "ZONE_1LABEL);
		free(file);
	}

	// Default zonefile path.
	file = conf_zonefile(conf(), zone_3label);
	ok(file != NULL, "Get zonefile path for "ZONE_3LABEL);
	if (file != NULL) {
		ok(strcmp(file, "/tmp/abc.ab.a.zone") == 0,
		          "Zonefile path compare for "ZONE_3LABEL);
		free(file);
	}

	// Unknown zone zonefile path.
	file = conf_zonefile(conf(), zone_unknown);
	ok(file != NULL, "Get zonefile path for "ZONE_UNKNOWN);
	if (file != NULL) {
		ok(strcmp(file, "/tmp/unknown.zone") == 0,
		          "Zonefile path compare for "ZONE_UNKNOWN);
		free(file);
	}

	test_conf_free();
	knot_dname_free(zone_arpa, NULL);
	knot_dname_free(zone_root, NULL);
	knot_dname_free(zone_1label, NULL);
	knot_dname_free(zone_3label, NULL);
	knot_dname_free(zone_unknown, NULL);
}

static void test_mix_ref(void)
{
	const char *conf_string =
		"remote:\n"
		"  - id: r1\n"
		"    address: ::1\n"
		"  - id: r2\n"
		"    address: ::2\n"
		"  - id: r3\n"
		"    address: ::3\n"
		"  - id: r4\n"
		"    address: ::4\n"
		"  - id: r5\n"
		"    address: ::5\n"
		"remotes:\n"
		"  - id: rs2\n"
		"    remote: [r2]\n"
		"  - id: rs45\n"
		"    remote: [r4, r5]\n"
		"\n"
		"submission:\n"
		"  - id: t1\n"
		"    parent: [r1, rs2, r3, rs45]\n"
		"  - id: t2\n"
		"    parent: [rs45, r2, r1]\n";

	int ret = test_conf(conf_string, NULL);
	is_int(KNOT_EOK, ret, "Prepare configuration");

	size_t cnt1 = 0;
	conf_val_t test1 = conf_rawid_get(conf(), C_SBM, C_PARENT, (const uint8_t *)"t1", 3);
	conf_mix_iter_t iter1;
	conf_mix_iter_init(conf(), &test1, &iter1);
	while (iter1.id->code == KNOT_EOK) {
		cnt1++;
		conf_mix_iter_next(&iter1);
	}
	is_int(5, cnt1, "number of mixed references 1");

	size_t cnt2 = 0;
	conf_val_t test2 = conf_rawid_get(conf(), C_SBM, C_PARENT, (const uint8_t *)"t2", 3);
	conf_mix_iter_t iter2;
	conf_mix_iter_init(conf(), &test2, &iter2);
	while (iter2.id->code == KNOT_EOK) {
		cnt2++;
		conf_mix_iter_next(&iter2);
	}
	is_int(4, cnt2, "number of mixed references 2");

	test_conf_free();
}

static void check_addrs(struct sockaddr_storage *addr, struct sockaddr_storage *via,
                        int family, const char *addr_str, const char *via_str)
{
	struct sockaddr_storage ref = { 0 };

	int set_ret = sockaddr_set(&ref, family, addr_str, 0);
	int cmp_ret = sockaddr_cmp(&ref, addr, true);
	is_int(KNOT_EOK, set_ret, "set address '%s'", addr_str);
	is_int(KNOT_EOK, cmp_ret, "cmp address '%s'", addr_str);

	if (via_str != NULL) {
		set_ret = sockaddr_set(&ref, family, via_str, 0);
		cmp_ret = sockaddr_cmp(&ref, via, true);
		is_int(KNOT_EOK, set_ret, "set via '%s'", via_str);
		is_int(KNOT_EOK, cmp_ret, "cmp via '%s'", via_str);
	} else {
		is_int(AF_UNSPEC, via->ss_family, "empty via");
	}
}

static void test_conf_remote(void)
{
	const char *conf_string =
		"remote:\n"
		"  - id: r1\n"
		"    address: [1::2, 1.0.0.2, 2::2, 3::2, 2.0.0.2]\n"
		"    via:     [1::1, 2::1, 1.0.0.1, 2.0.0.1]\n"
		"  - id: r2\n"
		"    address: [1::2, 1.0.0.2, 2::2]\n"
		"    via:     [1::1, 2::1, 3::1]\n"
		"template:\n"
		"  - id: t1\n"
		"    notify: r1\n"
		"  - id: t2\n"
		"    notify: r2\n";

	int ret = test_conf(conf_string, NULL);
	is_int(KNOT_EOK, ret, "Prepare configuration");

	conf_remote_t r;
	conf_val_t id;

	id = conf_rawid_get(conf(), C_TPL, C_NOTIFY, (const uint8_t *)"t1", 3);
	r = conf_remote(conf(), &id, 0);
	check_addrs(&r.addr, &r.via, AF_INET6, "1::2", "1::1");
	r = conf_remote(conf(), &id, 1);
	check_addrs(&r.addr, &r.via, AF_INET, "1.0.0.2", "1.0.0.1");
	r = conf_remote(conf(), &id, 2);
	check_addrs(&r.addr, &r.via, AF_INET6, "2::2", "2::1");
	r = conf_remote(conf(), &id, 3);
	check_addrs(&r.addr, &r.via, AF_INET6, "3::2", "2::1");
	r = conf_remote(conf(), &id, 4);
	check_addrs(&r.addr, &r.via, AF_INET, "2.0.0.2", "2.0.0.1");

	id = conf_rawid_get(conf(), C_TPL, C_NOTIFY, (const uint8_t *)"t2", 3);
	r = conf_remote(conf(), &id, 0);
	check_addrs(&r.addr, &r.via, AF_INET6, "1::2", "1::1");
	r = conf_remote(conf(), &id, 1);
	check_addrs(&r.addr, &r.via, AF_INET, "1.0.0.2", NULL);
	r = conf_remote(conf(), &id, 2);
	check_addrs(&r.addr, &r.via, AF_INET6, "2::2", "2::1");

	test_conf_free();
}

int main(int argc, char *argv[])
{
	plan_lazy();

	diag("get_filename");
	test_get_filename();

	diag("conf_zonefile");
	test_conf_zonefile();

	diag("mixed references");
	test_mix_ref();

	diag("conf_remote");
	test_conf_remote();

	return 0;
}
