/*
 * Copyright (C) 2024 iopsys Software Solutions AB
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation
 *
 *	  Author: Suvendhu Hansa <suvendhu.hansa@iopsys.eu>
 *
 */

#include "libbbfdm-api/dmcommon.h"
#include <dirent.h>
#include <linux/limits.h>
#include <unistd.h>
#include <sys/stat.h>

#define HWMON_PATH "/sys/class/hwmon"
#define THERMAL_PATH "/sys/class/thermal"

static int read_sysfs_int(const char *path, int *value)
{
	char buf[64] = {0};
	char *endptr;
	long val;

	if (dm_read_sysfs_file(path, buf, sizeof(buf)) < 0)
		return -1;

	errno = 0;
	val = strtol(buf, &endptr, 10);
	if (errno != 0 || endptr == buf || val > INT_MAX || val < INT_MIN)
		return -1;

	*value = (int)val;
	return 0;
}

static int read_sysfs_string(const char *path, char *buf, size_t buf_size)
{
	if (dm_read_sysfs_file(path, buf, buf_size) < 0)
		return -1;

	/* Remove trailing newline */
	size_t len = strlen(buf);
	if (len > 0 && buf[len - 1] == '\n')
		buf[len - 1] = '\0';

	return 0;
}

static int write_sysfs_int(const char *path, int value)
{
	FILE *f;
	int ret;

	// cppcheck-suppress cert-MSC24-C
	f = fopen(path, "w");
	if (!f)
		return -1;

	ret = fprintf(f, "%d\n", value);
	fclose(f);

	return (ret > 0) ? 0 : -1;
}

static void derive_alarm_path(const char *temp_input_path, const char *alarm_type, char *alarm_path, size_t alarm_path_size)
{
	char *last_underscore;
	char base_path[512];

	/* Copy the input path */
	snprintf(base_path, sizeof(base_path), "%s", temp_input_path);

	/* Find last underscore (before "input") and replace with alarm type */
	last_underscore = strrchr(base_path, '_');
	if (last_underscore) {
		*last_underscore = '\0';
		snprintf(alarm_path, alarm_path_size, "%s_%s", base_path, alarm_type);
	} else {
		alarm_path[0] = '\0';
	}
}

static int write_alarm_value(const char *sysfs_path, int trip_point_num, const char *hwmon_alarm_type, int value_millidegrees)
{
	char alarm_path[512];

	/* WiFi sensors don't have sysfs paths */
	if (!sysfs_path || sysfs_path[0] == '\0')
		return -1;

	/* For thermal zones: try trip_point_X_temp */
	if (strstr(sysfs_path, "thermal_zone")) {
		char *last_slash = strrchr(sysfs_path, '/');
		if (last_slash) {
			char zone_dir[480];  /* Leave room for suffix in alarm_path */
			size_t dir_len = last_slash - sysfs_path;
			if (dir_len >= sizeof(zone_dir))
				dir_len = sizeof(zone_dir) - 1;
			snprintf(zone_dir, dir_len + 1, "%s", sysfs_path);
			snprintf(alarm_path, sizeof(alarm_path), "%s/trip_point_%d_temp", zone_dir, trip_point_num);
			if (write_sysfs_int(alarm_path, value_millidegrees) == 0)
				return 0;
		}
	}

	/* For hwmon: try temp*_<alarm_type> */
	derive_alarm_path(sysfs_path, hwmon_alarm_type, alarm_path, sizeof(alarm_path));
	if (write_sysfs_int(alarm_path, value_millidegrees) == 0)
		return 0;

	return -1;
}

static int read_alarm_value(const char *sysfs_path, int trip_point_num, const char *hwmon_alarm_type, int *value_millidegrees)
{
	char alarm_path[512];

	/* WiFi sensors don't have sysfs paths */
	if (!sysfs_path || sysfs_path[0] == '\0')
		return -1;

	/* For thermal zones: try trip_point_X_temp */
	if (strstr(sysfs_path, "thermal_zone")) {
		char *last_slash = strrchr(sysfs_path, '/');
		if (last_slash) {
			char zone_dir[480];  /* Leave room for suffix in alarm_path */
			size_t dir_len = last_slash - sysfs_path;
			if (dir_len >= sizeof(zone_dir))
				dir_len = sizeof(zone_dir) - 1;
			snprintf(zone_dir, dir_len + 1, "%s", sysfs_path);
			snprintf(alarm_path, sizeof(alarm_path), "%s/trip_point_%d_temp", zone_dir, trip_point_num);
			if (read_sysfs_int(alarm_path, value_millidegrees) == 0)
				return 0;
		}
	}

	/* For hwmon: try temp*_<alarm_type> */
	derive_alarm_path(sysfs_path, hwmon_alarm_type, alarm_path, sizeof(alarm_path));
	if (read_sysfs_int(alarm_path, value_millidegrees) == 0)
		return 0;

	return -1;
}

static void scan_hwmon_sensors(json_object *sensors_array)
{
	DIR *dir;
	struct dirent *entry;

	dir = opendir(HWMON_PATH);
	if (!dir)
		return;

	while ((entry = readdir(dir)) != NULL) {
		if (entry->d_name[0] == '.')
			continue;

		char hwmon_dir[280];  /* HWMON_PATH (17) + "/" + d_name (255) + null */
		char real_path[512];
		snprintf(hwmon_dir, sizeof(hwmon_dir), "%s/%s", HWMON_PATH, entry->d_name);

		/* Resolve symlink to check if this is a thermal zone hwmon interface */
		if (realpath(hwmon_dir, real_path) != NULL) {
			/* Skip if this hwmon device belongs to a thermal zone */
			/* Thermal zones create hwmon interfaces under /sys/devices/virtual/thermal/thermal_zoneX/hwmonY */
			if (strstr(real_path, "/thermal/thermal_zone") != NULL)
				continue;
		}

		/* Scan for tempX_input files */
		for (int i = 1; i <= 10; i++) {
			char temp_path[512];
			char label_path[512];
			char name_path[512];
			char name[280] = {0};  /* d_name (255) + "_temp" + digit + null */
			int temp_value = 0;

			snprintf(temp_path, sizeof(temp_path), "%s/temp%d_input", hwmon_dir, i);

			if (read_sysfs_int(temp_path, &temp_value) < 0)
				continue;

			/* Try to read sensor label first, then hwmon name */
			snprintf(label_path, sizeof(label_path), "%s/temp%d_label", hwmon_dir, i);
			if (read_sysfs_string(label_path, name, sizeof(name)) < 0) {
				char tmp[128] = {0};

				snprintf(name_path, sizeof(name_path), "%s/name", hwmon_dir);
				if (read_sysfs_string(name_path, tmp, sizeof(tmp)) < 0) {
					snprintf(name, sizeof(name), "hwmon_%s_temp%d", entry->d_name, i);
				} else {
					if (strncmp(tmp, "mt7915", 6) == 0) {
						snprintf(name, sizeof(name), "wifi_radio%s", tmp+6);
					} else {
						snprintf(name, sizeof(name), "hwmon_%s_temp_%d", tmp, i);
					}
				}
			}

			/* Create JSON object for this sensor */
			json_object *sensor = json_object_new_object();
			json_object_object_add(sensor, "name", json_object_new_string(name));
			json_object_object_add(sensor, "sysfs_path", json_object_new_string(temp_path));
			json_object_object_add(sensor, "temperature", json_object_new_int(temp_value / 1000));
			json_object_array_add(sensors_array, sensor);
		}
	}

	closedir(dir);
}

static int interface_exists(const char *ifname)
{
	char path[256];
	struct stat st;

	/* Validate interface name to prevent path traversal */
	if (!ifname || ifname[0] == '\0' || strchr(ifname, '/') || strchr(ifname, '.'))
		return 0;

	snprintf(path, sizeof(path), "/sys/class/net/%s", ifname);
	return (stat(path, &st) == 0 && S_ISDIR(st.st_mode));
}

static void scan_mwctl_sensors(json_object *sensors_array)
{
	const char *interfaces[] = {"ra0", "rai0", "rax0", NULL};
	const char *phy[] = {"phy0", "phy1", "phy2", NULL};
	char output[4096];
	int i;

	for (i = 0; interfaces[i] != NULL; i++) {
		const char *ifname = interfaces[i];
		const char *phyname = phy[i];
		FILE *fp;
		int found_temp = 0;
		int temp_value = 0;
		char cmd[256];

		if (!interface_exists(ifname))
			continue;

		/* Build command for popen - interface name is validated */
		snprintf(cmd, sizeof(cmd), "/usr/sbin/mwctl %s stat 2>/dev/null", ifname);

		fp = popen(cmd, "r"); // flawfinder: ignore
		if (!fp)
			continue;

		while (fgets(output, sizeof(output), fp) != NULL) {
			/* Look for "CurrentTemperature = <value>" */
			if (strstr(output, "CurrentTemperature") != NULL) {
				char *equals = strchr(output, '=');
				if (equals) {
					char *endptr;
					long val = strtol(equals + 1, &endptr, 10);
					if (endptr != equals + 1 && val >= INT_MIN && val <= INT_MAX) {
						temp_value = (int)val;
						found_temp = 1;
						break;
					}
				}
			}
		}

		pclose(fp);

		if (found_temp) {
			char name[256];
			snprintf(name, sizeof(name), "wifi_radio_%s", phyname);

			json_object *sensor = json_object_new_object();
			json_object_object_add(sensor, "name", json_object_new_string(name));
			json_object_object_add(sensor, "sysfs_path", json_object_new_string(""));
			json_object_object_add(sensor, "temperature", json_object_new_int(temp_value));
			json_object_object_add(sensor, "wifi_interface", json_object_new_string(ifname));
			json_object_object_add(sensor, "wifi_type", json_object_new_string("mwctl"));
			json_object_array_add(sensors_array, sensor);
		}
	}
}

static void scan_wlctl_sensors(json_object *sensors_array)
{
	const char *interfaces[] = {"wl0", "wl1", "wl2", NULL};
	char output[256];
	int i;

	for (i = 0; interfaces[i] != NULL; i++) {
		const char *ifname = interfaces[i];
		FILE *fp;
		int temp_value = 0;
		int found_temp = 0;
		char cmd[256];

		if (!interface_exists(ifname))
			continue;

		/* Build command for popen - interface name is validated */
		snprintf(cmd, sizeof(cmd), "/usr/sbin/wlctl -i %s phy_tempsense 2>/dev/null", ifname);

		fp = popen(cmd, "r"); // flawfinder: ignore
		if (!fp)
			continue;

		if (fgets(output, sizeof(output), fp) != NULL) {
			/* Output is typically just a number or "Temperature: <value>" */
			char *endptr;
			long val = strtol(output, &endptr, 10);
			if (endptr != output && val >= 0 && val <= INT_MAX) {
				temp_value = (int)val;
				found_temp = 1;
			}
		}

		pclose(fp);

		if (found_temp) {
			char name[256];
			snprintf(name, sizeof(name), "wifi_radio_%s", ifname);

			json_object *sensor = json_object_new_object();
			json_object_object_add(sensor, "name", json_object_new_string(name));
			json_object_object_add(sensor, "sysfs_path", json_object_new_string(""));
			json_object_object_add(sensor, "temperature", json_object_new_int(temp_value));
			json_object_object_add(sensor, "wifi_interface", json_object_new_string(ifname));
			json_object_object_add(sensor, "wifi_type", json_object_new_string("wlctl"));
			json_object_array_add(sensors_array, sensor);
		}
	}
}

static void scan_thermal_zones(json_object *sensors_array)
{
	DIR *dir;
	struct dirent *entry;

	dir = opendir(THERMAL_PATH);
	if (!dir)
		return;

	while ((entry = readdir(dir)) != NULL) {
		if (strncmp(entry->d_name, "thermal_zone", 12) != 0)
			continue;

		char zone_dir[280];  /* THERMAL_PATH (19) + "/" + d_name (255) + null */
		char temp_path[512];
		char type_path[512];
		char type[128] = {0};
		char name[256] = {0};
		int temp_value = 0;

		snprintf(zone_dir, sizeof(zone_dir), "%s/%s", THERMAL_PATH, entry->d_name);
		snprintf(temp_path, sizeof(temp_path), "%s/temp", zone_dir);
		snprintf(type_path, sizeof(type_path), "%s/type", zone_dir);

		if (read_sysfs_int(temp_path, &temp_value) < 0)
			continue;

		/* Try to read the zone type for name */
		if (read_sysfs_string(type_path, type, sizeof(type)) == 0 && type[0] != '\0') {
			snprintf(name, sizeof(name), "%s", type);
		} else {
			snprintf(name, sizeof(name), "%s", entry->d_name);
		}

		/* Create JSON object for this sensor */
		json_object *sensor = json_object_new_object();
		json_object_object_add(sensor, "name", json_object_new_string(name));
		json_object_object_add(sensor, "sysfs_path", json_object_new_string(temp_path));
		json_object_object_add(sensor, "temperature", json_object_new_int(temp_value / 1000));
		json_object_array_add(sensors_array, sensor);
	}

	closedir(dir);
}

static void scan_xpon_sensors(json_object *sensors_array)
{
	FILE *fp;
	char output[256];
	int temp_value = 0;

	fp = popen("ubus -t 1 call xpon status 2>/dev/null | jsonfilter -e '@.ONU[0].ANI[0].Transceiver[0].Temperature'", "r"); // flawfinder: ignore
	if (!fp)
		return;

	if (fgets(output, sizeof(output), fp) != NULL) {
		char *endptr;
		long val = strtol(output, &endptr, 10);
		if (endptr != output && val >= INT_MIN && val <= INT_MAX) {
			temp_value = (int)val;

			/* Create JSON object for optical transceiver sensor */
			json_object *sensor = json_object_new_object();
			json_object_object_add(sensor, "name", json_object_new_string("optical_transceiver"));
			json_object_object_add(sensor, "sysfs_path", json_object_new_string(""));
			json_object_object_add(sensor, "temperature", json_object_new_int(temp_value));
			json_object_object_add(sensor, "source", json_object_new_string("xpon"));
			json_object_array_add(sensors_array, sensor);
		}
	}

	pclose(fp);
}

static void discover_sensors(json_object **sensors_data)
{
	*sensors_data = json_object_new_array();

	/* Scan hwmon sensors */
	scan_hwmon_sensors(*sensors_data);

	/* Scan thermal zones */
	scan_thermal_zones(*sensors_data);

	/* Scan WiFi sensors via mwctl (MediaTek) */
	scan_mwctl_sensors(*sensors_data);

	/* Scan WiFi sensors via wlctl (Broadcom) */
	scan_wlctl_sensors(*sensors_data);

	/* Scan optical transceiver sensors via xpon */
	scan_xpon_sensors(*sensors_data);
}

static int browseTemperatureSensor(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
	json_object *sensors_data = NULL;
	struct dm_data data = {0};
	int instance_id = 0;

	discover_sensors(&sensors_data);

	if (sensors_data) {
		int array_len = json_object_array_length(sensors_data);

		for (int i = 0; i < array_len; i++) {
			json_object *sensor_obj = json_object_array_get_idx(sensors_data, i);
			if (!sensor_obj)
				continue;

			data.json_object = sensor_obj;

			char *inst = handle_instance_without_section(dmctx, parent_node, ++instance_id);
			if (DM_LINK_INST_OBJ(dmctx, parent_node, (void *)&data, inst) == DM_STOP)
				break;
		}

		json_object_put(sensors_data);
	}

	return 0;
}

/*************************************************************
 * GET/SET PARAMETER FUNCTIONS
 *************************************************************/

static int get_TemperatureStatus_numentries(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	int cnt = get_number_of_entries(ctx, data, instance, browseTemperatureSensor);
	dmasprintf(value, "%d", cnt);
	return 0;
}

static int get_TemperatureSensor_alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	struct uci_section *s = NULL;

	uci_path_foreach_option_eq(bbfdm, "dmmap", "temperature", "temperature_instance", instance, s) {
		dmuci_get_value_by_section_string(s, "alias", value);
		break;
	}
	if ((*value)[0] == '\0')
		dmasprintf(value, "cpe-%s", instance);
	return 0;
}

static int set_TemperatureSensor_alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	struct uci_section *s = NULL, *dmmap = NULL;
	switch (action) {
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, value, -1, 64, NULL, NULL))
				return FAULT_9007;
			break;
		case VALUESET:
			uci_path_foreach_option_eq(bbfdm, "dmmap", "temperature", "temperature_instance", instance, s) {
				dmuci_set_value_by_section_bbfdm(s, "alias", value);
				return 0;
			}
			dmuci_add_section_bbfdm("dmmap", "temperature", &dmmap);
			dmuci_set_value_by_section(dmmap, "temperature_instance", instance);
			dmuci_set_value_by_section(dmmap, "alias", value);
			break;
	}
	return 0;
}

static int get_TemperatureSensor_name(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	json_object *obj = ((struct dm_data *)data)->json_object;
	*value = dmjson_get_value(obj, 1, "name");
	return 0;
}

static int get_TemperatureSensor_value(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	json_object *obj = ((struct dm_data *)data)->json_object;
	*value = dmjson_get_value(obj, 1, "temperature");
	return 0;
}

static int set_alarm_value_common(struct dmctx *ctx, void *data, char *value, int action, int trip_point_num, const char *hwmon_alarm_type)
{
	json_object *obj = ((struct dm_data *)data)->json_object;
	const char *sysfs_path;
	int alarm_value_celsius;

	switch (action) {
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, value, -1, -1, NULL, NULL))
				return FAULT_9007;
			{
				char *endptr;
				long val = strtol(value, &endptr, 10);
				if (endptr == value || val < -274 || val > INT_MAX)
					return FAULT_9007;
				alarm_value_celsius = (int)val;
			}
			break;
		case VALUESET:
			sysfs_path = dmjson_get_value(obj, 1, "sysfs_path");
			if (!sysfs_path || sysfs_path[0] == '\0')
				return FAULT_9002;

			{
				char *endptr;
				long val = strtol(value, &endptr, 10);
				if (endptr == value || val < INT_MIN || val > INT_MAX)
					return FAULT_9002;
				alarm_value_celsius = (int)val;
			}
			if (alarm_value_celsius == -274) {
				/* -274 means disable/unconfigure - skip writing */
				return 0;
			}

			if (write_alarm_value(sysfs_path, trip_point_num, hwmon_alarm_type, alarm_value_celsius * 1000) != 0)
				return FAULT_9002;
			break;
	}
	return 0;
}

static int get_TemperatureSensor_low_alarm_value(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	json_object *obj = ((struct dm_data *)data)->json_object;
	const char *sysfs_path = dmjson_get_value(obj, 1, "sysfs_path");
	int alarm_value;

	*value = dmstrdup("-274"); /* Default: not configured */

	if (!sysfs_path || sysfs_path[0] == '\0')
		return 0;

	/* trip_point_1_temp for thermal zones, temp*_crit for hwmon */
	if (read_alarm_value(sysfs_path, 1, "crit", &alarm_value) == 0) {
		dmasprintf(value, "%d", alarm_value / 1000);
	}

	return 0;
}

static int set_TemperatureSensor_low_alarm_value(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	/* trip_point_1_temp for thermal zones, temp*_crit for hwmon */
	return set_alarm_value_common(ctx, data, value, action, 1, "crit");
}

static int get_TemperatureSensor_high_alarm_value(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	json_object *obj = ((struct dm_data *)data)->json_object;
	const char *sysfs_path = dmjson_get_value(obj, 1, "sysfs_path");
	int alarm_value;

	*value = dmstrdup("-274"); /* Default: not configured */

	if (!sysfs_path || sysfs_path[0] == '\0')
		return 0;

	/* trip_point_0_temp for thermal zones, temp*_max for hwmon */
	if (read_alarm_value(sysfs_path, 0, "max", &alarm_value) == 0) {
		dmasprintf(value, "%d", alarm_value / 1000);
	}

	return 0;
}

static int set_TemperatureSensor_high_alarm_value(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	/* trip_point_0_temp for thermal zones, temp*_max for hwmon */
	return set_alarm_value_common(ctx, data, value, action, 0, "max");
}

/******************************************************************************************************************************
*                                            OBJ & PARAM DEFINITION
*******************************************************************************************************************************/
static DMLEAF tTemperatureStatusTemperatureSensorParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type*/
{"Alias", &DMWRITE, DMT_STRING, get_TemperatureSensor_alias, set_TemperatureSensor_alias, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"Name", &DMREAD, DMT_STRING, get_TemperatureSensor_name, NULL, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"Value", &DMREAD, DMT_INT, get_TemperatureSensor_value, NULL, BBFDM_BOTH},
{"LowAlarmValue", &DMWRITE, DMT_INT, get_TemperatureSensor_low_alarm_value, set_TemperatureSensor_low_alarm_value, BBFDM_BOTH},
{"HighAlarmValue", &DMWRITE, DMT_INT, get_TemperatureSensor_high_alarm_value, set_TemperatureSensor_high_alarm_value, BBFDM_BOTH},
{0}
};

/* *** Device.DeviceInfo.TemperatureStatus *** */
DMOBJ tDeviceInfoTemperatureStatusObj[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type, uniqueKeys*/
{"TemperatureSensor", &DMREAD, NULL, NULL, NULL, browseTemperatureSensor, NULL, NULL, NULL, tTemperatureStatusTemperatureSensorParams, NULL, BBFDM_BOTH},
{0}
};

DMLEAF tDeviceInfoTemperatureStatusParams[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type, uniqueKeys*/
{"TemperatureSensorNumberOfEntries", &DMREAD, DMT_UNINT, get_TemperatureStatus_numentries, NULL, BBFDM_BOTH},
{0}
};
