/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * uci.c - helper functions for UCI config file.
 *
 * Copyright (C) 2024 Iopsys Software Solutions AB. All rights reserved.
 * Copyright (C) 2025 Genexis AB.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <limits.h>
#include <net/if.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>
#include <libubus.h>
#include <uci.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <easy/easy.h>
#include <wifi.h>
#include <wifidefs.h>

#include "wifimngr.h"
#include "debug.h"

static enum wifi_band uci_to_radio_band(const char *uci_band)
{
	if (!uci_band)
		return BAND_UNKNOWN;

	if (!strcmp(uci_band, "2g"))
		return BAND_2;
	else if (!strcmp(uci_band, "5g"))
		return BAND_5;
	else if (!strcmp(uci_band, "6g"))
		return BAND_6;
	else
		return BAND_UNKNOWN;
}

static void uci_add_option(struct uci_context *ctx, struct uci_package *p,
			   struct uci_section *s, const char *option,
			   void *value, bool is_list)
{
	struct uci_ptr ptr = { 0 };

	ptr.p = p;
	ptr.s = s;
	ptr.package = p->e.name;
	ptr.section = s->e.name;
	ptr.option = option;
	ptr.target = UCI_TYPE_OPTION;
	ptr.flags |= UCI_LOOKUP_EXTENDED;
	ptr.value = (char *)value;

	if (is_list)
		uci_add_list(ctx, &ptr);
	else
		uci_set(ctx, &ptr);
}


static int uci_get_wifi_mld_netdev(const char *mld, char *netdev)
{
	struct uci_context *ctx;
	struct uci_element *e;
	struct uci_ptr ptr;
	char uci_path[128] = {};

	ctx = uci_alloc_context();
	if (!ctx)
		return -1;

	snprintf(uci_path, sizeof(uci_path), "wireless.%s.ifname", mld);

	if (uci_lookup_ptr(ctx, &ptr, uci_path, true) != UCI_OK) {
		uci_free_context(ctx);
		return -1;
	}

	e = ptr.last;
	if (e->type != UCI_TYPE_OPTION) {
		uci_free_context(ctx);
		return -1;
	}

	if (ptr.o->type != UCI_TYPE_STRING) {
		uci_free_context(ctx);
		return -1;
	}

	memset(netdev, 0, 15);
	strncpy(netdev, ptr.o->v.string, 15);

	uci_free_context(ctx);
	return 0;
}

static enum wifi_band uci_get_iface_band(struct wifimngr *w, const char *device)
{
	int i;

	for (i = 0; i < w->num_wifi_device; i++) {
		if (strcmp(device, w->wdev[i].device))
			continue;
		return w->wdev[i].band;
	}

	return BAND_UNKNOWN;
}

static bool uci_get_iface_mlo(struct wifimngr *w, const char *device)
{
	int i;

	for (i = 0; i < w->num_wifi_device; i++) {
		if (strcmp(device, w->wdev[i].device))
			continue;
		return w->wdev[i].mlo;
	}

	return false;
}


int uci_add_wifi_iface(char **argv)
{
	struct uci_context *ctx;
	struct uci_package *pkg;
	struct uci_section *sec;
	struct uci_element *e;
	struct uci_option *op;
	char *ifname = NULL;
	bool found = false;
	int i = 0;

	ctx = uci_alloc_context();
	if (!ctx)
		return -1;

	if (uci_load(ctx, "wireless", &pkg)) {
		uci_free_context(ctx);
		return -1;
	}

	while (argv[i]) {
		if (!strcmp(argv[i], "ifname")) {
			ifname = argv[i + 1];
			break;
		}
		i += 2;
	}

	if (ifname) {
		uci_foreach_element(&pkg->sections, e) {
			struct uci_element *x, *tmp;

			sec = uci_to_section(e);

			if (strcmp(sec->type, "wifi-iface"))
				continue;

			uci_foreach_element_safe(&sec->options, tmp, x) {
				op = uci_to_option(x);
				if (op->type == UCI_TYPE_STRING &&
				    !strncmp(x->name, "ifname", 6) &&
				    !strncmp(op->v.string, ifname, 15)) {

					found = true;
					break;
				}
			}

			if (found) {
				wifimngr_warn("%s %s already added\n", __func__, ifname);
				break;
			}
		}
	}

	if (!found)
		uci_add_section(ctx, pkg, "wifi-iface", &sec);

	i = 0;
	while (argv[i]) {
		uci_add_option(ctx, pkg, sec, argv[i], argv[i + 1], false);
		i += 2;
	}

	uci_commit(ctx, &pkg, false);
	uci_free_context(ctx);

	return 0;
}

int uci_del_wifi_iface(char *ifname)
{
	struct uci_context *ctx;
	struct uci_package *pkg;
	struct uci_element *e;
	struct uci_option *op;
	bool found = false;

	ctx = uci_alloc_context();
	if (!ctx)
		return -1;

	if (uci_load(ctx, "wireless", &pkg)) {
		uci_free_context(ctx);
		return -1;
	}

	uci_foreach_element(&pkg->sections, e) {
		struct uci_section *s = uci_to_section(e);
		struct uci_element *x, *tmp;

		if (strcmp(s->type, "wifi-iface"))
			continue;

		uci_foreach_element_safe(&s->options, tmp, x) {
			op = uci_to_option(x);
			if (op->type == UCI_TYPE_STRING &&
				!strncmp(x->name, "ifname", 6) &&
				!strncmp(op->v.string, ifname, 15)) {

				found = true;
				break;
			}
		}

		if (found) {
			struct uci_ptr ptr = { 0 };

			ptr.p = pkg;
			ptr.s = s;
			ptr.package = pkg->e.name;
			ptr.section = s->e.name;
			ptr.option = NULL;
			ptr.target = UCI_TYPE_SECTION;
			ptr.flags |= UCI_LOOKUP_EXTENDED;
			ptr.value = "wifi-iface";

			uci_delete(ctx, &ptr);
			uci_commit(ctx, &pkg, false);
			break;
		}
	}

	uci_free_context(ctx);

	return found ? 0 : -1;
}

int uci_get_wifi_devices(struct wifimngr *w, const char *conffile)
{
	struct uci_context *ctx;
	struct uci_package *pkg;
	struct uci_element *e;

	ctx = uci_alloc_context();
	if (!ctx)
		return -1;

	if (uci_load(ctx, conffile, &pkg)) {
		uci_free_context(ctx);
		return -1;
	}


	uci_foreach_element(&pkg->sections, e) {
		struct uci_section *s = uci_to_section(e);
		if (!strcmp(s->type, "wifi-device")) {
			struct uci_element *x;
			struct uci_option *op;
			//int idx;


			//FIXME
			//idx = wifimngr_lookup_wifi_device(w, s->e.name);

			strncpy(w->wdev[w->num_wifi_device].device, s->e.name, 15);
			strncpy(w->wdev[w->num_wifi_device].phy, s->e.name, 15);

			uci_foreach_element(&s->options, x) {
				op = uci_to_option(x);
				if (!strncmp(x->name, "path", 4) &&
				    op->type == UCI_TYPE_STRING) {
					char phy[16] = {0};
					int ret;

					ret = find_phy_from_device_path(op->v.string, phy, sizeof(phy) - 1);
					if (!ret) {
						memset(w->wdev[w->num_wifi_device].phy, 0,
						       sizeof(w->wdev[w->num_wifi_device].phy));
						strncpy(w->wdev[w->num_wifi_device].phy, phy,
							sizeof(w->wdev[w->num_wifi_device].phy));
					}
				} else if (!strncmp(x->name, "macaddr", 7) &&
					   op->type == UCI_TYPE_STRING) {
					uint8_t macaddr[6] = {0};

					if (hwaddr_aton(op->v.string, macaddr) && !hwaddr_is_zero(macaddr))
						memcpy(w->wdev[w->num_wifi_device].macaddr, macaddr, 6);

					wifimngr_dbg("%s: device = %s,  macaddr = " MACFMT"\n", __func__,
						     w->wdev[w->num_wifi_device].device,
						     MAC2STR(w->wdev[w->num_wifi_device].macaddr));
				}
			}

			/* Get band */
			uci_foreach_element(&s->options, x) {
				op = uci_to_option(x);
				if (!strncmp(x->name, "band", 4) &&
				    op->type == UCI_TYPE_STRING) {
					w->wdev[w->num_wifi_device].band = uci_to_radio_band(op->v.string);
				}
			}

			/* Get disabled */
			uci_foreach_element(&s->options, x) {
				op = uci_to_option(x);
				if (!strncmp(x->name, "disabled", 8) &&
				    op->type == UCI_TYPE_STRING) {
					w->wdev[w->num_wifi_device].disabled = atoi(op->v.string);
				}
			}

			/* Get mlo_capable */
			uci_foreach_element(&s->options, x) {
				op = uci_to_option(x);
				if (!strncmp(x->name, "mlo_capable", 11) &&
				    op->type == UCI_TYPE_STRING) {
					w->wdev[w->num_wifi_device].mlo_capable = atoi(op->v.string);
				} else if (!strncmp(x->name, "mlo", 3) &&
				    op->type == UCI_TYPE_STRING) {
					w->wdev[w->num_wifi_device].mlo = atoi(op->v.string);
				}
			}

			/* Get country */
			memset(w->wdev[w->num_wifi_device].country, 0, sizeof(w->wdev[w->num_wifi_device].country));
			uci_foreach_element(&s->options, x) {
				op = uci_to_option(x);
				if (!strncmp(x->name, "country", 7) &&
				    op->type == UCI_TYPE_STRING) {
					strncpy(w->wdev[w->num_wifi_device].country, op->v.string, 2);
				}
			}

			/* ACS */
			uci_foreach_element(&s->options, x) {
				op = uci_to_option(x);
				if (!strncmp(x->name, "channel", 8) &&
				    op->type == UCI_TYPE_STRING) {
					w->wdev[w->num_wifi_device].acs_enabled = !strncmp(op->v.string, "auto", 4);
				}
			}

			w->num_wifi_device++;
			if (w->num_wifi_device == WIFI_DEV_MAX_NUM)
				break;
		}
	}

	uci_free_context(ctx);
	return w->num_wifi_device;
}

int uci_get_wifi_interfaces(struct wifimngr *w, const char *conffile)
{
	struct uci_context *ctx;
	struct uci_package *pkg;
	struct uci_element *e;

	ctx = uci_alloc_context();
	if (!ctx)
		return -1;

	if (uci_load(ctx, conffile, &pkg)) {
		uci_free_context(ctx);
		return -1;
	}


	uci_foreach_element(&pkg->sections, e) {
		struct uci_section *s = uci_to_section(e);

		if (!strcmp(s->type, "wifi-iface")) {
			struct uci_element *x;
			struct uci_option *op;

			uci_foreach_element(&s->options, x) {
				op = uci_to_option(x);
				if (!strncmp(x->name, "ifname", 6) &&
					op->type == UCI_TYPE_STRING) {

					strncpy(w->ifs[w->num_wifi_iface].iface, op->v.string, 15);
				} else if (!strncmp(x->name, "device", 6) &&
					op->type == UCI_TYPE_STRING) {

					strncpy(w->ifs[w->num_wifi_iface].device, op->v.string, 15);
					w->ifs[w->num_wifi_iface].band = uci_get_iface_band(w, w->ifs[w->num_wifi_iface].device);
					w->ifs[w->num_wifi_iface].mlo = uci_get_iface_mlo(w, w->ifs[w->num_wifi_iface].device);
				} else if (!strncmp(x->name, "disabled", 8) &&
					   op->type == UCI_TYPE_STRING) {

					w->ifs[w->num_wifi_iface].disabled = !!atoi(op->v.string);
				} else if (!strncmp(x->name, "mode", 4) &&
					op->type == UCI_TYPE_STRING) {

					if (!strncmp(op->v.string, "ap", 2))
						w->ifs[w->num_wifi_iface].mode = WIFI_MODE_AP;
					else if (!strncmp(op->v.string, "wet", 3))
						w->ifs[w->num_wifi_iface].mode = WIFI_MODE_STA;
					else if (!strncmp(op->v.string, "sta", 3))
						w->ifs[w->num_wifi_iface].mode = WIFI_MODE_STA;
					else
						w->ifs[w->num_wifi_iface].mode = WIFI_MODE_UNKNOWN;
				} else if (!strncmp(x->name, "mld", 3) &&
					   op->type == UCI_TYPE_STRING) {

					/* Check if radio/device MLO enabled */
					if (uci_get_iface_mlo(w, w->ifs[w->num_wifi_iface].device)) {
						strncpy(w->ifs[w->num_wifi_iface].mld, op->v.string, 15);
						uci_get_wifi_mld_netdev(w->ifs[w->num_wifi_iface].mld,
									w->ifs[w->num_wifi_iface].mld_netdev);
					}
				}
			}

			if (strlen(w->ifs[w->num_wifi_iface].iface) == 0)
				snprintf(w->ifs[w->num_wifi_iface].iface, 16, "idx%d", w->num_wifi_iface);

			w->num_wifi_iface++;
			if (w->num_wifi_iface == WIFI_IF_MAX_NUM)
				break;
		}
	}

	uci_free_context(ctx);
	return w->num_wifi_iface;
}

static enum wifi_mode uci_get_mld_mode_from_iface(struct wifimngr *w, char *mldname)
{
	enum wifi_mode mode = WIFI_MODE_UNKNOWN;
	int i;

	for (i = 0; i < w->num_wifi_iface; i++) {
		if (!strlen(w->ifs[i].mld))
			continue;
		if (strcmp(mldname, w->ifs[i].mld_netdev))
			continue;

		mode = w->ifs[i].mode;
		break;
	}

	return mode;
}

int uci_get_wifi_mlds(struct wifimngr *w, const char *conffile)
{
	struct uci_context *ctx;
	struct uci_package *pkg;
	struct uci_element *e;

	ctx = uci_alloc_context();
	if (!ctx)
		return -1;

	if (uci_load(ctx, conffile, &pkg)) {
		uci_free_context(ctx);
		return -1;
	}


	uci_foreach_element(&pkg->sections, e) {
		struct uci_section *s = uci_to_section(e);

		if (!strcmp(s->type, "wifi-mld")) {
			struct wifimngr_mld *mld;
			struct uci_element *x;
			struct uci_option *op;

			mld = &w->mld[w->num_wifi_mld];
			uci_foreach_element(&s->options, x) {
				op = uci_to_option(x);
				if (!strncmp(x->name, "ifname", 6) &&
					op->type == UCI_TYPE_STRING) {
					strncpy(mld->ifname, op->v.string, 15);
				}

				if (!strncmp(x->name, "mode", 4) &&
					     op->type == UCI_TYPE_STRING) {
					if (!strcmp(op->v.string, "sta"))
						mld->mode = WIFI_MODE_STA;
					else if (!strcmp(op->v.string, "ap"))
						mld->mode = WIFI_MODE_AP;
					else
						mld->mode = WIFI_MODE_UNKNOWN;
				}
			}

			if (strlen(mld->ifname) == 0)
				snprintf(mld->ifname, 16, "mldif.%d", w->num_wifi_mld);

			/* If not set directly in mld, fallback to wifi-iface */
			if (mld->mode == WIFI_MODE_UNKNOWN)
				mld->mode = uci_get_mld_mode_from_iface(w, mld->ifname);

			w->num_wifi_mld++;
			if (w->num_wifi_mld == WIFI_MLD_MAX_NUM)
				break;
		}
	}

	uci_free_context(ctx);
	return w->num_wifi_mld;
}


