/* SPDX-License-Identifier: BSD-3-Clause */
/*
 * i1905_cmd.c - contains CLI commands for status and mgmt of ieee1905d.
 *
 * Copyright (C) 2023-2024 IOPSYS Software Solutions AB. All rights reserved.
 * Copyright (C) 2025 Genexis AB.
 *
 * Author: anjan.chanda@iopsys.eu
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <stdbool.h>
#include <errno.h>

#include <json-c/json.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>

#include "debug.h"
#include "util.h"
#include "timer.h"
#include "1905_tlvs.h"
#include "cmdu.h"
#include "cmdu_ackq.h"
#include "cmdufrag.h"
#include "i1905_dm.h"
#include "config.h"
#include "i1905.h"
#include "i1905_wifi.h"

static int i1905_handle_command(struct i1905_private *priv, int cli,
				char *cmdname, char *args)
{
	if (!strcmp(cmdname, "info")) {
		struct blob_buf bb = {0};
		char *jresp;

		blob_buf_init(&bb, 0);
		i1905_dump_info(priv, &bb);

		jresp = blobmsg_format_json(bb.head, true);
		if (!jresp) {
			blob_buf_free(&bb);
			return -1;
		}

		dprintf(cli, "%s\n", jresp);
		blob_buf_free(&bb);
		free(jresp);
	} else if (!strcmp(cmdname, "neighbors")) {
		struct blob_buf bb = {0};
		char *jresp;

		blob_buf_init(&bb, 0);
		i1905_dump_neighbors(priv, &bb);

		jresp = blobmsg_format_json(bb.head, true);
		if (!jresp) {
			blob_buf_free(&bb);
			return -1;
		}

		dprintf(cli, "%s\n", jresp);
		blob_buf_free(&bb);
		free(jresp);
	} else if (!strcmp(cmdname, "others")) {
		struct blob_buf bb = {0};
		char *jresp;

		blob_buf_init(&bb, 0);
		i1905_dump_non1905neighbors(priv, &bb);

		jresp = blobmsg_format_json(bb.head, true);
		if (!jresp) {
			blob_buf_free(&bb);
			return -1;
		}

		dprintf(cli, "%s\n", jresp);
		blob_buf_free(&bb);
		free(jresp);
	} else if (!strcmp(cmdname, "links")) {
		struct blob_buf bb = {0};
		char *jresp;

		blob_buf_init(&bb, 0);
		i1905_dump_links(priv, &bb);

		jresp = blobmsg_format_json(bb.head, true);
		if (!jresp) {
			blob_buf_free(&bb);
			return -1;
		}

		dprintf(cli, "%s\n", jresp);
		blob_buf_free(&bb);
		free(jresp);
	} else if (!strcmp(cmdname, "cmdu")) {
		struct blob_buf bo = {0};
		struct blob_buf bi = {0};
		char *jresp;

		blob_buf_init(&bo, 0);
		blob_buf_init(&bi, 0);
		if (!blobmsg_add_json_from_string(&bi, args)) {
			i1905_dbg(LOG_CMD, "%s: blobmsg from json-string error\n", __func__);
			blob_buf_free(&bi);
			return -1;
		}

		/*
		struct blob_buf bp = {0};
		struct blob_attr *attr;
		size_t rem;

		blob_buf_init(&bp, 0);
		blobmsg_for_each_attr(attr, bi.head, rem) {
			blobmsg_add_blob(&bp, attr);
		}
		*/

		i1905_do_cmdu_tx(priv, bi.head, &bo);

		jresp = blobmsg_format_json(bo.head, true);
		if (!jresp) {
			blob_buf_free(&bo);
			blob_buf_free(&bi);
			return -1;
		}

		dprintf(cli, "%s\n", jresp);
		blob_buf_free(&bi);
		blob_buf_free(&bo);
		free(jresp);
	} else if (!strcmp(cmdname, "rxcmdu")) {
		struct blob_buf bo = {0};
		struct blob_buf bi = {0};
		char *jresp;

		blob_buf_init(&bo, 0);
		blob_buf_init(&bi, 0);
		if (args && strlen(args) && !blobmsg_add_json_from_string(&bi, args)) {
			i1905_dbg(LOG_CMD, "%s: blobmsg from json-string error\n", __func__);
			blob_buf_free(&bi);
			return -1;
		}

		i1905_do_cmdu_rx(priv, bi.head, &bo);

		jresp = blobmsg_format_json(bo.head, true);
		if (!jresp) {
			blob_buf_free(&bo);
			blob_buf_free(&bi);
			return -1;
		}

		dprintf(cli, "%s\n", jresp);
		blob_buf_free(&bi);
		blob_buf_free(&bo);
		free(jresp);
	} else if (!strcmp(cmdname, "buildcmdu")) {
		struct blob_buf bo = {0};
		struct blob_buf bi = {0};
		char *jresp;

		blob_buf_init(&bo, 0);
		blob_buf_init(&bi, 0);
		if (args && strlen(args) && !blobmsg_add_json_from_string(&bi, args)) {
			i1905_dbg(LOG_CMD, "%s: blobmsg from json-string error\n", __func__);
			blob_buf_free(&bi);
			return -1;
		}

		i1905_do_cmdu_prepare(priv, bi.head, &bo);

		jresp = blobmsg_format_json(bo.head, true);
		if (!jresp) {
			blob_buf_free(&bo);
			blob_buf_free(&bi);
			return -1;
		}

		dprintf(cli, "%s\n", jresp);
		blob_buf_free(&bi);
		blob_buf_free(&bo);
		free(jresp);
	} else if (!strcmp(cmdname, "apconfig")) {
		struct blob_buf bi = {0};
		struct blob_buf bo = {0};
		char *jresp;

		blob_buf_init(&bo, 0);
		blob_buf_init(&bi, 0);
		if (args && strlen(args) && !blobmsg_add_json_from_string(&bi, args)) {
			i1905_dbg(LOG_CMD, "%s: blobmsg from json-string error\n", __func__);
			blob_buf_free(&bi);
			return -1;
		}

		i1905_do_apconfig(priv, bi.head, &bo);
		jresp = blobmsg_format_json(bo.head, true);
		if (!jresp) {
			blob_buf_free(&bo);
			return -1;
		}

		dprintf(cli, "%s\n", jresp);
		blob_buf_free(&bo);
		free(jresp);
	} else if (!strcmp(cmdname, "add_interface")) {
		struct blob_buf bo = {0};
		struct blob_buf bi = {0};
		char *jresp;

		blob_buf_init(&bo, 0);
		blob_buf_init(&bi, 0);
		if (!blobmsg_add_json_from_string(&bi, args)) {
			i1905_dbg(LOG_CMD, "%s: blobmsg from json-string error\n", __func__);
			blob_buf_free(&bi);
			return -1;
		}

		i1905_interface_add(priv, bi.head, &bo);
		jresp = blobmsg_format_json(bo.head, true);
		if (!jresp) {
			blob_buf_free(&bo);
			blob_buf_free(&bi);
			return -1;
		}

		dprintf(cli, "%s\n", jresp);
		blob_buf_free(&bi);
		blob_buf_free(&bo);
		free(jresp);
	} else if (!strcmp(cmdname, "del_interface")) {
		struct blob_buf bo = {0};
		struct blob_buf bi = {0};
		char *jresp;

		blob_buf_init(&bo, 0);
		blob_buf_init(&bi, 0);
		if (!blobmsg_add_json_from_string(&bi, args)) {
			i1905_dbg(LOG_CMD, "%s: blobmsg from json-string error\n", __func__);
			blob_buf_free(&bi);
			return -1;
		}

		i1905_interface_del(priv, bi.head, &bo);
		jresp = blobmsg_format_json(bo.head, true);
		if (!jresp) {
			blob_buf_free(&bo);
			blob_buf_free(&bi);
			return -1;
		}

		dprintf(cli, "%s\n", jresp);
		blob_buf_free(&bi);
		blob_buf_free(&bo);
		free(jresp);
	} else {
		dprintf(cli, "Unknown command: %s\n", cmdname);
	}

	return 0;
}

static void i1905_process_cmdline(struct i1905_private *priv, int cli, char *buffer)
{
	char *cmdline, *cmdname, *args;
	char *line, *ptr, *saveptr;
	struct json_object *jmsg;
	int i = 0;


	ptr = buffer;
	line = strtok_r(ptr, "\n", &saveptr);
	if (!line)
		return;

	ptr = saveptr;
	if (strlen(line) == 0)
		return;

	cmdline = strdup(line);
	cmdname = strtok_r(cmdline, " ", &args);
	if (!cmdname) {
		i1905_dbg(LOG_CMD, "%s: No command name\n", __func__);
		goto out;
	}

	i1905_dbg(LOG_CMD, "%s: cli command: %s\n", __func__, cmdname);
	if (args && strlen(args)) {
		i1905_dbg(LOG_CMD, "%s:        args: %s (len = %zu)\n", __func__, args, strlen(args));
		while (i <= strlen(args) && args[i++] == ' ')
			; /* NOOP */
		if (i <= strlen(args)) {
			/* json validate the passed args */
			jmsg = json_tokener_parse(args);
			if (!jmsg) {
				i1905_dbg(LOG_CMD, "%s: Invalid json cmdline\n", __func__);
				goto out;
			}

			if (!json_object_is_type(jmsg, json_type_object)) {
				json_object_put(jmsg);
				goto out;
			}
		}
	}

	i1905_handle_command(priv, cli, cmdname, args);
out:
	free(cmdline);
}

static void i1905_recv_cmd(struct uloop_fd *fd, unsigned int events)
{
	struct i1905_private *priv =
				container_of(fd, struct i1905_private, cmdloop);

	for (;;) {
		struct sockaddr_un cliaddr = {0};
		char buffer[1024] = {0};
		socklen_t len = sizeof(struct sockaddr_un);
		int cli;
		int n;

		cli = accept(priv->cmdsock, (struct sockaddr *)&cliaddr, &len);
		if (cli < 0) {
			//i1905_dbg(LOG_CMD, "%s: accept() %s\n", __func__, strerror(errno));
			return;
		}

		n = read(cli, buffer, sizeof(buffer));
		if (n == -1) {
			if (errno == EAGAIN || errno == EWOULDBLOCK)
				return;

			break;
		}

		if (n == 0)
			return;

		i1905_process_cmdline(priv, cli, buffer);
		close(cli);
	}

	if (!priv->cmdloop.registered) {
		dbg("%s: uloop re-registering for cli commands\n", __func__);
		uloop_fd_delete(&priv->cmdloop);
		uloop_fd_add(&priv->cmdloop, ULOOP_READ /* | ULOOP_EDGE_TRIGGER */);
	}
}

int i1905_start_cmdserver(struct i1905_private *priv, struct i1905_useropts *u)
{
	char cmdpath[96] = {0};
	struct sockaddr_un sa;
	int reuse = 1;
	int ret;
	int s;

	memset(&sa, 0, sizeof(sa));
	sa.sun_family = AF_UNIX;
	snprintf(cmdpath, sizeof(cmdpath), "%s.%ld.cmd", u->cmdpath, (long)getpid());
	strncpy(sa.sun_path, cmdpath, strlen(cmdpath));
	s = socket(AF_UNIX, SOCK_STREAM, 0);
	if (s < 0) {
		i1905_warn(LOG_CMD, "%s: socket() %s\n", __func__, strerror(errno));
		return -1;
	}

	unlink(u->cmdpath);
	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) != 0) {
		i1905_warn(LOG_CMD, "%s: setsockopt() %s\n", __func__, strerror(errno));
		close(s);
		return -1;
	}

	if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) != 0) {
		i1905_warn(LOG_CMD, "%s: bind() %s\n", __func__, strerror(errno));
		close(s);
		return -1;
	}

	listen(s, 1);
	priv->cmdpath = strdup(cmdpath);
	priv->cmdsock = s;
	priv->cmdloop.fd = s;
	priv->cmdloop.cb = i1905_recv_cmd;
	ret = uloop_fd_add(&priv->cmdloop, ULOOP_READ /* | ULOOP_EDGE_TRIGGER */);
	if (ret) {
		close(s);
		priv->cmdsock = -1;
		i1905_warn(LOG_CMD, "%s: uloop_fd_add() %s\n", __func__, strerror(errno));
		return -1;
	}

	i1905_dbg(LOG_CMD, "%s: listen for commands on '%s'\n", __func__, u->cmdpath);
	return 0;
}

void i1905_stop_cmdserver(struct i1905_private *priv)
{
	if (priv && priv->cmdsock > 0) {
		uloop_fd_delete(&priv->cmdloop);
		priv->cmdloop.fd = -1;
		close(priv->cmdsock);
		unlink(priv->cmdpath);
	}
}
