// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2025 Olivier Gournet

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

#include <gr_api.h>
#include <gr_ip6.h>
#include <gr_net_types.h>

#include <ecoli.h>

#include <errno.h>
#include <signal.h>
#include <sys/queue.h>
#include <unistd.h>

static volatile sig_atomic_t stop;

static void sighandler(int) {
	stop = true;
}

static cmd_status_t icmp_send(
	struct gr_api_client *c,
	struct gr_ip6_icmp_send_req *req,
	uint16_t msdelay,
	uint16_t count,
	uint16_t ident,
	bool mode_traceroute
) {
	struct gr_ip6_icmp_recv_resp *reply_resp;
	struct gr_ip6_icmp_recv_req reply_req;
	int i, timeout, ret, errors;
	void *resp_ptr = NULL;
	const char *errdesc;

	stop = false;
	errors = 0;
	errno = 0;

	for (i = !!mode_traceroute; i < count && stop == false; i++) {
		req->ident = ident;
		req->seq_num = i;
		req->ttl = mode_traceroute ? i : 64;

		ret = gr_api_client_send_recv(c, GR_IP6_ICMP6_SEND, sizeof(*req), req, NULL);
		if (ret < 0) {
			errors++;
			break;
		}

		reply_req.ident = ident;
		reply_req.seq_num = i;
		timeout = 50;
		do {
			usleep(10000);
			ret = gr_api_client_send_recv(
				c, GR_IP6_ICMP6_RECV, sizeof(reply_req), &reply_req, &resp_ptr
			);
			if (resp_ptr != NULL)
				break;
		} while (ret == -ENOENT && --timeout > 0);

		if (ret < 0 && ret != -ENOENT) {
			errors++;
			break;
		}

		reply_resp = resp_ptr;
		if (reply_resp == NULL || timeout == 0) {
			errors++;
			errno = ETIMEDOUT;
			if (mode_traceroute)
				printf("%2d  timeout\n", i);
			else
				printf("timeout: icmp_seq=%d\n", i);
		} else {
			double roundtrip_ms = (reply_resp->response_time * 1000.) / GR_NS_PER_S;
			switch (reply_resp->type) {
			case ICMP6_TYPE_ECHO_REPLY:
				if (!mode_traceroute) {
					printf("reply from " IP6_F ": icmp_seq=%d ttl=%d "
					       "time=%.3f ms\n",
					       &reply_resp->src_addr,
					       reply_resp->seq_num,
					       reply_resp->ttl,
					       roundtrip_ms);
				} else {
					printf("%2d  " IP6_F " time=%.3f ms\n",
					       i,
					       &reply_resp->src_addr,
					       roundtrip_ms);
					stop = true;
					errors = 0;
				}
				errno = 0;
				break;
			case ICMP6_TYPE_ERR_DEST_UNREACH:
				errno = EHOSTUNREACH;
				errdesc = " destination unreachable";
				break;
			case ICMP6_TYPE_ERR_PKT_TOO_BIG:
				errno = EINVAL;
				errdesc = " packet too big";
				break;
			case ICMP6_TYPE_ERR_TTL_EXCEEDED:
				errno = ETIMEDOUT;
				errdesc = " ttl exceeded";
				break;
			case ICMP6_TYPE_ERR_PARAM_PROBLEM:
			default:
				errno = EINVAL;
				errdesc = " parameter problem";
				break;
			}
			if (errno && !mode_traceroute) {
				errors++;
				printf("reply from " IP6_F ": icmp_seq=%d ttl=%d:%s\n",
				       &reply_resp->src_addr,
				       reply_resp->seq_num,
				       reply_resp->ttl,
				       errdesc);
			} else if (errno && mode_traceroute) {
				errors++;
				if (errno == ETIMEDOUT)
					errdesc = "";
				printf("%2d  " IP6_F " time=%.3f ms%s\n",
				       i,
				       &reply_resp->src_addr,
				       roundtrip_ms,
				       errdesc);
			}
			free(resp_ptr);
			resp_ptr = NULL;
		}
		usleep(msdelay * 1000);
	}

	if (!stop && errors > 0)
		return CMD_ERROR;

	return CMD_SUCCESS;
}

static cmd_status_t ping(struct gr_api_client *c, const struct ec_pnode *p) {
	struct gr_ip6_icmp_send_req req = {.iface = GR_IFACE_ID_UNDEF, .vrf = 0};
	cmd_status_t ret = CMD_ERROR;
	uint16_t count = UINT16_MAX;
	uint16_t ident = random();
	uint16_t msdelay = 1000;

	if (arg_ip6(p, "DEST", &req.addr) < 0)
		return CMD_ERROR;
	if (arg_vrf(c, p, "VRF", &req.vrf) < 0)
		return CMD_ERROR;
	if (arg_u16(p, "COUNT", &count) < 0 && errno != ENOENT)
		return CMD_ERROR;
	if (arg_u16(p, "DELAY", &msdelay) < 0 && errno != ENOENT)
		return CMD_ERROR;
	if (arg_u16(p, "IDENT", &ident) < 0 && errno != ENOENT)
		return CMD_ERROR;
	if (arg_iface(c, p, "IFACE", GR_IFACE_TYPE_UNDEF, &req.iface) < 0 && errno != ENOENT)
		return CMD_ERROR;

	struct sigaction sa = {.sa_handler = sighandler}, old;
	sigaction(SIGINT, &sa, &old);

	ret = icmp_send(c, &req, msdelay, count, ident, false);

	sigaction(SIGINT, &old, NULL);

	return ret;
}

static cmd_status_t traceroute(struct gr_api_client *c, const struct ec_pnode *p) {
	struct gr_ip6_icmp_send_req req = {.iface = GR_IFACE_ID_UNDEF, .vrf = 0};
	cmd_status_t ret = CMD_SUCCESS;
	uint16_t ident = random();

	if (arg_ip6(p, "DEST", &req.addr) < 0)
		return CMD_ERROR;
	if (arg_vrf(c, p, "VRF", &req.vrf) < 0)
		return CMD_ERROR;
	if (arg_u16(p, "IDENT", &ident) < 0 && errno != ENOENT)
		return CMD_ERROR;
	if (arg_iface(c, p, "IFACE", GR_IFACE_TYPE_UNDEF, &req.iface) < 0 && errno != ENOENT)
		return CMD_ERROR;

	struct sigaction sa = {.sa_handler = sighandler}, old;
	sigaction(SIGINT, &sa, &old);

	ret = icmp_send(c, &req, 0, 255, ident, true);

	sigaction(SIGINT, &old, NULL);

	return ret;
}

static struct cli_icmp_ops ops = {
	.af = GR_AF_IP6,
	.ping = ping,
	.traceroute = traceroute,

};

static void __attribute__((constructor, used)) init(void) {
	cli_icmp_ops_register(&ops);
}
