/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * wifimngr_event.c - handle wifi events
 *
 * Copyright (C) 2019-2024 Iopsys Software Solutions AB. All rights reserved.
 * Copyright (C) 2025 Genexis AB.
 *
 * Author: Anjan Chanda <anjan.chanda@genexis.eu>
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/prctl.h>
#include <net/if.h>
#include <sys/types.h>
#include <dirent.h>

#include <libubox/blobmsg_json.h>
#include <libubus.h>

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

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

#define MAX_EVENT_RESPONSE_LEN		3000

struct wifimngr_event {
	struct uloop_fd uloop_fd;
	struct list_head list;
	struct event_struct event;
	void *handle;
};

static int wifimngr_ubus_event(struct ubus_context *ctx, char *message)
{
	char event[32] = {0};
	char *data;
	struct blob_buf b;

	data = calloc(1, MAX_EVENT_RESPONSE_LEN);
	if (!data)
		return 0;

	if (WARN_ON(strlen(message) > MAX_EVENT_RESPONSE_LEN))
		goto out;

	if (WARN_ON(sscanf(message, "%31s '%2999[^\n]'", event, data) != 2))
		goto out;

	/* ignore non-wifi events */
	if (!strstr(event, "wifi."))
		goto out;

	memset(&b, 0, sizeof(b));
	blob_buf_init(&b, 0);

	if (!blobmsg_add_json_from_string(&b, data)) {
		wifimngr_err("Failed to parse message data: %s\n", data);
		blob_buf_free(&b);
		free(data);
		return -1;
	}

	wm_info(LOG_EVENT, "%s\n", message);
	ubus_send_event(ctx, event, b.head);
	blob_buf_free(&b);
out:
	free(data);
	return 0;
}

int wifimngr_event_cb(struct event_struct *e)
{
	struct wifimngr *w = (struct wifimngr *)e->priv;
	struct event_response *resp = &e->resp;
	const char *evtype = NULL;
	char *evtbuf;

	evtbuf = calloc(1, MAX_EVENT_RESPONSE_LEN);
	if (!evtbuf)
		return 0;

	switch (resp->type) {
	case WIFI_EVENT_SCAN_START:
	case WIFI_EVENT_SCAN_END:
		evtype = resp->type == WIFI_EVENT_SCAN_END ? "scan_finished" : "scan_started";
		if (WARN_ON(resp->len >= MAX_EVENT_RESPONSE_LEN))
			break;

		if (resp->len > 0) {
			snprintf(evtbuf, MAX_EVENT_RESPONSE_LEN - 1,
				 "%s '{\"ifname\":\"%s\", \"event\":\"%s\", \"data\": %s}'",
				 WIFI_RADIO_OBJECT, e->ifname, evtype, resp->data);
		} else {
			snprintf(evtbuf, MAX_EVENT_RESPONSE_LEN - 1,
				 "%s '{\"ifname\":\"%s\", \"event\":\"%s\"}'",
				 WIFI_RADIO_OBJECT, e->ifname, evtype);
		}
		wifimngr_ubus_event(w->ubus_ctx, evtbuf);
#ifdef WIFI_CACHE_SCANRESULTS
		if (resp->type == WIFI_EVENT_SCAN_END) {
			struct wifimngr_device *wdev;

			wdev = wifimngr_ifname_to_device(w, e->ifname);
			if (wdev) {
				wifimngr_update_scanresults_cache(wdev,
								  resp->len,
								  (char *)resp->data);
			}
		}
#endif
		break;
	case WIFI_EVENT_SCAN_ABORT:
		snprintf(evtbuf, MAX_EVENT_RESPONSE_LEN - 1,
			 "%s '{\"ifname\":\"%s\", \"event\":\"scan_aborted\"}'",
			 WIFI_RADIO_OBJECT, e->ifname);
		wifimngr_ubus_event(w->ubus_ctx, evtbuf);
		break;
	default:
		/* the default event handler */
		if (WARN_ON(resp->len >= MAX_EVENT_RESPONSE_LEN))
			break;

		strncpy(evtbuf, (char *)resp->data, resp->len);
		wifimngr_ubus_event(w->ubus_ctx, evtbuf);
		break;
	}

	free(evtbuf);
	return 0;
}

static void wifimngr_event_uloop_cb(struct uloop_fd *fd, unsigned int events)
{
	struct wifimngr_event *ev = (void *) fd;
	int rv;

	for (;;) {
		rv = wifi_recv_event(ev->event.ifname, ev->handle);
		if (rv < 0)
			break;
	}

	if (rv == -ENETDOWN) {
		wifimngr_dbg("%s read error ENETDOWN - rearm uloop fd monitor\n", ev->event.ifname);
		uloop_fd_delete(&ev->uloop_fd);
		uloop_fd_add(&ev->uloop_fd, ULOOP_READ | ULOOP_EDGE_TRIGGER);
	}
}

void wifimngr_event_exit(struct wifimngr *w)
{
	struct wifimngr_event *ev, *tmp;

	list_for_each_entry_safe(ev, tmp, &w->event_list, list) {
		wifimngr_dbg("remove %s %s %s\n", ev->event.ifname, ev->event.family, ev->event.group);
		uloop_fd_delete(&ev->uloop_fd);
		wifi_unregister_event(ev->event.ifname, ev->handle);
		list_del(&ev->list);
		free(ev->event.resp.data);
		free(ev);
	}

	list_del_init(&w->event_list);
}

int wifimngr_events_register(struct wifimngr *w, const char *ifname,
			     const char *family, const char *group)
{
	char prefix[64] = {};
	struct dirent *p;
	char *ptr;
	DIR *d;
	int i;


	if (!ifname)
		return wifimngr_event_register(w, NULL, family, group);

	ptr = strstr(ifname, "*");
	if (!ptr)
		return wifimngr_event_register(w, ifname, family, group);

	strncpy(prefix, ifname, sizeof(prefix) - 1);
	prefix[ptr - ifname] = '\0';

	wifimngr_dbg("events_register prefix %s\n", prefix);

	d = opendir("/sys/class/net");
	if (WARN_ON(!d))
		return -1;

	while ((p = readdir(d))) {
		if (!strcmp(p->d_name, ""))
			continue;
		if (!strcmp(p->d_name, "."))
			continue;
		if (!strcmp(p->d_name, ".."))
			continue;
		if (strncmp(p->d_name, prefix, strlen(prefix)))
			continue;

		WARN_ON(wifimngr_event_register(w, p->d_name, family, group));
	}

	closedir(d);

	for (i = 0; i < w->num_wifi_iface; i++) {
		if (strncmp(w->ifs[i].iface, prefix, strlen(prefix)))
			continue;

		WARN_ON(wifimngr_event_register(w, w->ifs[i].iface, family, group));
	}

	return 0;
}

int wifimngr_event_register(struct wifimngr *w, const char *ifname,
			    const char *family, const char *group)
{
	struct wifimngr_event *ev;

	list_for_each_entry(ev, &w->event_list, list) {
		if (strcmp(family, ev->event.family))
			continue;
		if (strcmp(group, ev->event.group))
			continue;
		if (ifname && strcmp(ifname, ev->event.ifname))
			continue;

		/* Already registered */
		wifimngr_dbg("%s %s %s already registered\n",
			ev->event.ifname, ev->event.family, ev->event.group);
		return 0;
	}

	ev = calloc(1, sizeof(*ev));
	if (!ev) {
		wifimngr_err("%s %s %s calloc(ev) failed\n", ifname, family, group);
		return -1;
	}

	if (ifname)
		strncpy(ev->event.ifname, ifname, sizeof(ev->event.ifname) - 1);
	strncpy(ev->event.family, family, sizeof(ev->event.family) - 1);
	strncpy(ev->event.group, group, sizeof(ev->event.group) - 1);
	ev->event.priv = w;
	ev->event.cb = wifimngr_event_cb;

	/* setup response buffer */
	ev->event.resp.maxlen = ev->event.resp.len = MAX_EVENT_RESPONSE_LEN;
	ev->event.resp.data = calloc(MAX_EVENT_RESPONSE_LEN, sizeof(uint8_t));
	if (ev->event.resp.data == NULL) {
		wifimngr_err("%s %s %s calloc(resp) failed\n",
			ev->event.ifname, ev->event.family, ev->event.group);
		free(ev);
		return -1;
	}

	if (wifi_register_event((char *)ifname, &ev->event, &ev->handle)) {
		wifimngr_err("%s %s %s wifi_register_revent() failed\n",
			ev->event.ifname, ev->event.family, ev->event.group);
		free(ev->event.resp.data);
		free(ev);
		return -1;
	}

	if (ev->event.fd_monitor) {
		ev->uloop_fd.fd = ev->event.fd_monitor;
		ev->uloop_fd.cb = wifimngr_event_uloop_cb;

		if (uloop_fd_add(&ev->uloop_fd, ULOOP_READ | ULOOP_EDGE_TRIGGER)) {
			wifimngr_err("uloop_fd_add(%d) failed\n", ev->uloop_fd.fd);
			wifi_unregister_event(ev->event.ifname, ev->handle);
			free(ev->event.resp.data);
			free(ev);
			return -1;
		}
	}

	list_add_tail(&ev->list, &w->event_list);
	return 0;
}

void wifimngr_event_unregister(struct wifimngr *w, const char *ifname)
{
	struct wifimngr_event *ev, *tmp;

	if (!ifname)
		return;

	list_for_each_entry_safe(ev, tmp, &w->event_list, list) {
		if (strcmp(ifname, ev->event.ifname))
			continue;
		wifimngr_dbg("remove %s %s %s\n", ev->event.ifname, ev->event.family, ev->event.group);
		uloop_fd_delete(&ev->uloop_fd);
		wifi_unregister_event(ifname, ev->handle);
		list_del(&ev->list);
		free(ev->event.resp.data);
		free(ev);
	}
}
