// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2025 Robin Jarry

#include "cli.h"
#include "cli_iface.h"
#include "cli_l3.h"
#include "display.h"

#include <gr_infra.h>
#include <gr_net_types.h>

#include <ecoli.h>

#include <assert.h>
#include <errno.h>
#include <string.h>
#include <sys/queue.h>

static STAILQ_HEAD(, cli_addr_ops) addr_ops = STAILQ_HEAD_INITIALIZER(addr_ops);

void cli_addr_ops_register(struct cli_addr_ops *ops) {
	assert(ops != NULL);
	assert(ops->add != NULL);
	assert(ops->del != NULL);
	assert(ops->list != NULL);
	assert(ops->flush != NULL);
	struct cli_addr_ops *o;
	STAILQ_FOREACH (o, &addr_ops, next)
		assert(ops->af != o->af);
	STAILQ_INSERT_TAIL(&addr_ops, ops, next);
}

static cmd_status_t addr_add(struct gr_api_client *c, const struct ec_pnode *p) {
	struct cli_addr_ops *ops;
	uint8_t addr[64];

	STAILQ_FOREACH (ops, &addr_ops, next) {
		if (arg_ip_net(p, "ADDR", addr, false, ops->af) == 0)
			return ops->add(c, p);
	}

	errno = ENOPROTOOPT;
	return CMD_ERROR;
}

static cmd_status_t addr_del(struct gr_api_client *c, const struct ec_pnode *p) {
	struct cli_addr_ops *ops;
	uint8_t addr[64];

	STAILQ_FOREACH (ops, &addr_ops, next) {
		if (arg_ip_net(p, "ADDR", addr, false, ops->af) == 0)
			return ops->del(c, p);
	}

	errno = ENOPROTOOPT;
	return CMD_ERROR;
}

static cmd_status_t addr_flush(struct gr_api_client *c, const struct ec_pnode *p) {
	addr_family_t af = cli_parse_family(p);
	struct cli_addr_ops *ops;
	uint16_t iface_id;
	int ret = 0;

	if (arg_iface(c, p, "IFACE", GR_IFACE_TYPE_UNDEF, &iface_id) < 0)
		return CMD_ERROR;

	STAILQ_FOREACH (ops, &addr_ops, next) {
		if (af != GR_AF_UNSPEC && ops->af != af)
			continue;
		if ((ret = ops->flush(c, iface_id)) < 0)
			break;
	}

	return ret < 0 ? CMD_ERROR : CMD_SUCCESS;
}

static cmd_status_t addr_list(struct gr_api_client *c, const struct ec_pnode *p) {
	uint16_t iface_id = GR_IFACE_ID_UNDEF;
	addr_family_t af = cli_parse_family(p);
	struct cli_addr_ops *ops;
	int ret = 0;

	if (arg_iface(c, p, "IFACE", GR_IFACE_TYPE_UNDEF, &iface_id) < 0 && errno != ENOENT)
		return CMD_ERROR;

	struct gr_table *table = gr_table_new();
	gr_table_column(table, "IFACE", GR_DISP_LEFT); // 0
	gr_table_column(table, "FAMILY", GR_DISP_LEFT); // 1
	gr_table_column(table, "ADDRESS", GR_DISP_LEFT); // 2

	STAILQ_FOREACH (ops, &addr_ops, next) {
		if (af != GR_AF_UNSPEC && ops->af != af)
			continue;
		if ((ret = ops->list(c, iface_id, table)) < 0)
			break;
	}

	gr_table_free(table);

	return ret < 0 ? CMD_ERROR : CMD_SUCCESS;
}

#define ADDR_CTX(root) CLI_CONTEXT(root, CTX_ARG("address", "IP addresses."))

static int ctx_init(struct ec_node *root) {
	int ret;

	ret = CLI_COMMAND(
		ADDR_CTX(root),
		"add ADDR iface IFACE",
		addr_add,
		"Add an address to an interface.",
		with_help("IP address with prefix length.", ec_node_re("ADDR", IP_ANY_NET_RE)),
		with_help(
			"Interface name.",
			ec_node_dyn("IFACE", complete_iface_names, INT2PTR(GR_IFACE_TYPE_UNDEF))
		)
	);
	if (ret < 0)
		return ret;
	ret = CLI_COMMAND(
		ADDR_CTX(root),
		"del ADDR iface IFACE",
		addr_del,
		"Remove an address from an interface.",
		with_help("IP address with prefix length.", ec_node_re("ADDR", IP_ANY_NET_RE)),
		with_help(
			"Interface name.",
			ec_node_dyn("IFACE", complete_iface_names, INT2PTR(GR_IFACE_TYPE_UNDEF))
		)
	);
	if (ret < 0)
		return ret;
	ret = CLI_COMMAND(
		ADDR_CTX(root),
		"flush [FAMILY] iface IFACE",
		addr_flush,
		"Remove all addresses from an interface.",
		CLI_FAMILY_NODE("Only flush IPv4 addresses.", "Only flush IPv6 addresses."),
		with_help(
			"Interface name.",
			ec_node_dyn("IFACE", complete_iface_names, INT2PTR(GR_IFACE_TYPE_UNDEF))
		)
	);
	if (ret < 0)
		return ret;
	ret = CLI_COMMAND(
		ADDR_CTX(root),
		"[show] [(FAMILY),(iface IFACE)]",
		addr_list,
		"Display interface addresses.",
		CLI_FAMILY_NODE("Only show IPv4 addresses.", "Only show IPv6 addresses."),
		with_help(
			"Interface name.",
			ec_node_dyn("IFACE", complete_iface_names, INT2PTR(GR_IFACE_TYPE_UNDEF))
		)
	);
	if (ret < 0)
		return ret;

	return 0;
}

static struct cli_context ctx = {
	.name = "address",
	.init = ctx_init,
};

static void __attribute__((constructor, used)) init(void) {
	cli_context_register(&ctx);
}
