/*
 * history.c - hosts' history management.
 *
 * Copyright (C) 2023 IOPSYS Software Solutions AB. All rights reserved.
 *
 * See LICENSE file for license related information.
 *
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <time.h>

#include <libubus.h>
#include <libubox/list.h>
#include <json-c/json.h>

#include <easy/easy.h>

#include "debug.h"
#include "util.h"
#include "timer.h"
#include "neigh.h"
#include "wifi_api.h"
#include "hostmngr.h"

int neigh_history_add_entry_json(struct json_object *jarray,
				 struct neigh_history_entry *he)
{
	struct json_object *e, *es;
	char macstr[18] = {0};
	char alstr[18] = {0};
	char ipstr[128] = {0};
	int remtime;

	if (!jarray || json_object_get_type(jarray) != json_type_array)
		return -1;

	hwaddr_ntoa(he->macaddr, macstr);
	hwaddr_ntoa(he->aladdr, alstr);
	remtime = timer_remaining_ms(&he->delete_timer) / 1000;
	inet_ntop(he->ip.family, &he->ip.addr, ipstr, sizeof(ipstr));

	e = json_object_new_object();
	es = json_object_new_object();
	json_object_object_add(e, "macaddr", json_object_new_string(macstr));
	json_object_object_add(e, "hostname", json_object_new_string(he->hostname));
	json_object_object_add(e, "ipaddr", json_object_new_string(ipstr));
	json_object_object_add(e, "device", json_object_new_string(he->ifname));
	json_object_object_add(e, "active_last_change", json_object_new_int(he->lastchange));
	json_object_object_add(e, "first_seen", json_object_new_int(he->createtime));
	json_object_object_add(e, "last_seen", json_object_new_int(he->lastseen));
	json_object_object_add(e, "ageout", json_object_new_int(remtime));
	json_object_object_add(e, "mediatype", json_object_new_int(he->type));
	json_object_object_add(e, "is1905", json_object_new_int(he->is1905));
	json_object_object_add(e, "is1905_interface", json_object_new_int(he->is1905_slave));
	json_object_object_add(e, "is1905_link", json_object_new_int(he->is1905_link));
	json_object_object_add(e, "ieee1905id", json_object_new_string(alstr));
	json_object_object_add(e, "stats", es);
	json_object_object_add(es, "tx_packets", json_object_new_int64(he->ws.ul_packets));
	json_object_object_add(es, "tx_bytes", json_object_new_int64(he->ws.ul_bytes));
	json_object_object_add(es, "rx_packets", json_object_new_int64(he->ws.dl_packets));
	json_object_object_add(es, "rx_bytes", json_object_new_int64(he->ws.dl_bytes));
	json_object_array_add(jarray, e);

	return 0;
}

int neigh_history_store_to_json_file(void *priv, const char *file)
{
	struct hostmngr_private *p = (struct hostmngr_private *)priv;
	struct neigh_queue *q = &p->neigh_q;
	struct json_object *jo, *jarray;
	int ret = 0;
	int i;


	dbg("%s: Num history = %u\n", __func__, q->num_history);
	jo = json_object_new_object();
	jarray = json_object_new_array_ext(q->num_history);
	if (!jarray) {
		ret = -1;
		goto err;
	}

	json_object_object_add(jo, "history", jarray);

	for (i = 0; i < NEIGH_ENTRIES_MAX; i++) {
		struct neigh_history_entry *e = NULL;

		if (hlist_empty(&q->history[i]))
			continue;

		hlist_for_each_entry(e, &q->history[i], hlist) {
			ret = neigh_history_add_entry_json(jarray, e);
			if (ret)
				goto err;
		}
	}

	json_object_to_file_ext(file, jo, JSON_C_TO_STRING_PRETTY);
err:
	json_object_put(jo);
	return ret;
}

int json_get_key_value(struct json_object *jo, const char *key, void *val)
{
	struct json_object *o = NULL;
	enum json_type otype;

	if (!json_object_object_get_ex(jo, key, &o)) {
		dbg("key %s does not exist\n", key);
		return -1;
	}

	otype = json_object_get_type(o);
	switch (otype) {
	case json_type_null:
		break;
	case json_type_boolean:
		(*(int *)val) = json_object_get_boolean(o);
		break;
	case json_type_double:
		(*(double *)val) = json_object_get_double(o);
		break;
	case json_type_int:
		(*(int *)val) = json_object_get_int(o);	//FIXME: (u)int64_t
		break;
	case json_type_string:
		memcpy(val, json_object_get_string(o), json_object_get_string_len(o));
		break;
	default:
		break;
	}

	return 0;
}

int neigh_history_load_from_json_file(void *priv, const char *file)
{
	struct json_object *jo, *jarray;
	const char *key = "history";
	time_t now = time(NULL);
	int len, i, ret = 0;


	jo = json_object_from_file(file);
	if (!jo) {
		fprintf(stderr, "failed to load json from '%s'\n", file);
		return -1;
	}

	if (!json_object_object_get_ex(jo, key, &jarray)) {
		fprintf(stderr, "'%s' not found in '%s'\n", key, file);
		ret = -1;
		goto err;
	}

	len = json_object_array_length(jarray);
	dbg("Num history entries in '%s' = %d\n", file, len);

	for (i = 0; i < len; i++) {
		struct neigh_history_entry he = {0};
		struct json_object *je, *je_ws;
		char macstr[18] = {0};
		char ipstr[128] = {0};
		char alstr[18] = {0};
		int is1905_slave = 0;
		int is1905_link = 0;
		int createtime = 0;
		int lastchange = 0;
		int lastseen = 0;
		int timeout = 0;
		int is1905 = 0;
		int type = 0;

		je = json_object_array_get_idx(jarray, i);
		json_get_key_value(je, "macaddr", macstr);
		if (!hwaddr_aton(macstr, he.macaddr)) {
			dbg("Invalid value in history!\n");
			ret = -1;
			goto err;
		}
		json_get_key_value(je, "hostname", he.hostname);
		json_get_key_value(je, "ipaddr", ipstr);
		json_get_key_value(je, "device", he.ifname);
		json_get_key_value(je, "active_last_change", &lastchange);
		json_get_key_value(je, "first_seen", &createtime);
		json_get_key_value(je, "last_seen", &lastseen);
		json_get_key_value(je, "ageout", &timeout);
		json_get_key_value(je, "mediatype", &type);
		json_get_key_value(je, "is1905", &is1905);
		json_get_key_value(je, "is1905_interface", &is1905_slave);
		json_get_key_value(je, "is1905_link", &is1905_link);
		json_get_key_value(je, "ieee1905id", alstr);
		if (!hwaddr_aton(alstr, he.aladdr)) {
			dbg("Invalid alid in history!\n");
			ret = -1;
			goto err;
		}

		json_object_object_get_ex(je, "stats", &je_ws);
		json_get_key_value(je_ws, "tx_packets", &he.ws.ul_packets);
		json_get_key_value(je_ws, "tx_bytes", &he.ws.ul_bytes);
		json_get_key_value(je_ws, "rx_packets", &he.ws.dl_packets);
		json_get_key_value(je_ws, "rx_bytes", &he.ws.dl_bytes);

		if (strlen(ipstr) == 16) {
			if (inet_pton(AF_INET, ipstr, &he.ip.addr.ip4) == 1)
				he.ip.family = AF_INET;
		} else {
			if (inet_pton(AF_INET6, ipstr, &he.ip.addr.ip6) == 1)
				he.ip.family = AF_INET6;
		}

		he.createtime = createtime;
		he.lastchange = lastchange;
		he.lastseen = lastseen;
		he.timeout = timeout;
		he.is1905 = is1905;
		he.is1905_slave = is1905_slave;
		he.is1905_link = is1905_link;
		he.type = type;

		dbg("macstr = '%s' (macaddr = " MACFMT" )\n", macstr, MAC2STR(he.macaddr));
		dbg("hostname = %s\n", he.hostname);
		dbg("device = %s\n", he.ifname);
		dbg("lastchange = %jd\n", (intmax_t)he.lastchange);
		dbg("firstseen = %jd\n", (intmax_t)he.createtime);
		dbg("lastseen = %jd\n", (intmax_t)he.lastseen);
		dbg("remtime = %u\n", he.timeout);
		dbg("mediatype = %d\n", he.type);
		dbg("is1905 = %d\n", he.is1905);
		dbg("is1905_interface = %d\n", he.is1905_slave);
		dbg("is1905_link = %d\n", he.is1905_link);
		dbg("ieee1905id = '%s' (macaddr = " MACFMT" )\n", alstr, MAC2STR(he.aladdr));
		dbg("tx-packets = %ju\n", (uintmax_t)he.ws.ul_packets);
		dbg("tx-bytes = %ju\n", (uintmax_t)he.ws.ul_bytes);
		dbg("rx-packets = %ju\n", (uintmax_t)he.ws.dl_packets);
		dbg("rx-bytes = %ju\n", (uintmax_t)he.ws.dl_bytes);

		dbg("%s: Now = %jd, lastseen = %jd, remtime = %u\n", __func__,
		    (intmax_t)now, (intmax_t)he.lastseen, he.timeout);

		if (difftime(now, (time_t)he.lastseen) > he.timeout) {
			dbg("Dropping stale history entry %d\n", i);
			continue;
		}

		dbg("Adding history entry " MACFMT " from file\n", MAC2STR(he.macaddr));
		neigh_history_entry_add(priv, &he);
	}

err:
	json_object_put(jo);
	return ret;
}

int hosts_store_to_json_file(void *priv, const char *file)
{
	struct hostmngr_private *p = (struct hostmngr_private *)priv;
	struct json_object *jo, *jarray;
	struct host_macaddr_entry *h;

	dbg("%s\n", __func__);
	jarray = json_object_new_array_ext(p->num_hosts);
	if (!jarray)
		return -1;

	jo = json_object_new_object();
	json_object_object_add(jo, "hosts", jarray);

	list_for_each_entry(h, &p->hostlist, list) {
		char macstr[18] = {0};

		hwaddr_ntoa(h->macaddr, macstr);
		json_object_array_add(jarray, json_object_new_string(macstr));
	}

	json_object_to_file_ext(file, jo, JSON_C_TO_STRING_PRETTY);
	json_object_put(jo);
	return 0;
}

int hosts_load_from_json_file(void *priv, const char *file)
{
	struct json_object *jo, *jarray;
	const char *key = "hosts";
	int len, i;


	dbg("%s\n", __func__);
	jo = json_object_from_file(file);
	if (!jo) {
		dbg("Failed to load json from '%s'\n", file);
		return -1;
	}

	if (!json_object_object_get_ex(jo, key, &jarray)) {
		dbg("Invalid json file: '%s' not found in '%s'\n", key, file);
		json_object_put(jo);
		return -1;
	}

	len = json_object_array_length(jarray);
	dbg("Num entries in '%s' = %d\n", file, len);

	for (i = 0; i < len; i++) {
		uint8_t macaddr[6] = {0};
		struct json_object *je;
		const char *jstr = NULL;

		je = json_object_array_get_idx(jarray, i);
		jstr = json_object_get_string(je);
		if (jstr) {
			char macstr[18] = {0};

			memcpy(macstr, jstr, strlen(jstr));
			if (!hwaddr_aton(macstr, macaddr)) {
				dbg("Invalid macaddr at index %d in %s\n", i, file);
				json_object_put(jo);
				return -1;
			}
			dbg("macstr = '%s' (" MACFMT ")\n", macstr, MAC2STR(macaddr));
		}

		hostlist_add_entry(priv, macaddr, true);
	}

	json_object_put(jo);
	return 0;
}
