/*
 * 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-ubus/bbfdm-ubus.h>

#include "dynamicdnsmngr.h"

static int init_ddnsmngr_module(void *data);
static int sync_ddnsmngr_module(void *data);

/* ********** DynamicObj ********** */
DM_MAP_OBJ tDynamicObj[] = {
/* parentobj, nextobject, parameter */
{"Device.", tDeviceObjs, NULL, init_ddnsmngr_module, NULL, sync_ddnsmngr_module},
{0}
};

#define DDNS_SERVICES_DEFAULT "/etc/ddnsmngr/servers"

/* The server override/tuning parameters (ServerAddress, ServerPort,
 * CheckInterval, RetryInterval, MaxRetries) drive ddnsmngr_updater.sh and are
 * therefore included only for the ddns-script backend. */
#ifndef DDNSMNGR_BACKEND_INADYN
#define DDNSMNGR_DDNSSCRIPT_BACKEND
#endif

static void cleanup_dangling_hosts(bool *commit_uci)
{
	struct uci_section *s = NULL, *tmp = NULL;

	if (commit_uci == NULL)
		return;

	*commit_uci = false;

	uci_foreach_sections_safe("ddnsmngr", "host", tmp, s) {
		char *parent = NULL;
		char sec_name[128] = {0};
		struct uci_section *dmmap = NULL, *cl_sec = NULL;

		snprintf(sec_name, sizeof(sec_name), "ddnsmngr.%s", section_name(s));
		dmmap = get_dup_section_in_dmmap_opt("DynamicDNS", "Hostname", "__section_name__", sec_name);

		dmuci_get_value_by_section_string(s, "dm_parent", &parent);
		if (DM_STRLEN(parent) != 0) {
			cl_sec = get_origin_section_from_config("ddnsmngr", "client", parent);
		}

		if (cl_sec == NULL) {
			if (dmmap != NULL) {
				dmuci_delete_by_section(dmmap, NULL, NULL);
			}

			dmuci_delete_by_section(s, NULL, NULL);
			*commit_uci = true;
		}
	}

	s = NULL;
	tmp = NULL;
	uci_path_foreach_sections_safe(bbfdm, "DynamicDNS", "Hostname", tmp, s) {
		char *config_section = NULL;
		struct uci_section *conf_sec = NULL;

		dmuci_get_value_by_section_string(s, "__section_name__", &config_section);
		if (DM_STRLEN(config_section) != 0) {
			conf_sec = get_config_section_from_dmmap_section_name(config_section);
		}

		if (conf_sec == NULL) {
			dmuci_delete_by_section(s, NULL, NULL);
		}
	}
}

static void dmmap_sync_ddns_client_host(struct dmctx *dmctx, struct uci_section *cl_sec)
{
	struct uci_section *s = NULL;

	if (cl_sec == NULL)
		return;

	uci_foreach_option_eq("ddnsmngr", "host", "dm_parent", section_name(cl_sec), s) {
		char sec_name[128] = {0};
		struct uci_section *dmmap = NULL;
		char *inst = NULL;

		snprintf(sec_name, sizeof(sec_name), "ddnsmngr.%s", section_name(s));
		dmmap = get_dup_section_in_dmmap_opt("DynamicDNS", "Hostname", "__section_name__", sec_name);
		if (dmmap == NULL) {
			create_dmmap_obj(dmctx, 1, "DynamicDNS", "Hostname", s, &inst);
		}
	}
}

static void dmmap_sync_ddnsmngr(struct dmctx *dmctx, const char *sec_type)
{
	struct uci_section *s = NULL;

	if (DM_STRLEN(sec_type) == 0)
		return;

	const char *dmmap_sec_type = (DM_STRCMP(sec_type, "client") == 0) ? "Client" : "Server";

	uci_foreach_sections("ddnsmngr", sec_type, s) {
		char sec_name[128] = {0};
		char *instance = NULL;

		snprintf(sec_name, sizeof(sec_name), "%s.%s", section_config(s), section_name(s));

		struct uci_section *dmmap_sec = get_dup_section_in_dmmap_opt("DynamicDNS", dmmap_sec_type, "__section_name__", sec_name);
		if (dmmap_sec == NULL) {
			create_dmmap_obj(dmctx, 0, "DynamicDNS", dmmap_sec_type, s, &instance);
		} else {
			dmuci_get_value_by_section_string(dmmap_sec, "__instance__", &instance);
			dmctx->obj_buf[0] = dmmap_sec_type;
			dmctx->inst_buf[0] = instance;
		}

		if (DM_STRCMP(sec_type, "client") == 0) {
			dmmap_sync_ddns_client_host(dmctx, s);
		}
	}

	struct uci_section *new_s = NULL, *tmp = NULL;
	uci_path_foreach_sections_safe(bbfdm, "DynamicDNS", dmmap_sec_type, tmp, new_s) {
		char *config_section = NULL;
		struct uci_section *conf_sec = NULL;

		dmuci_get_value_by_section_string(new_s, "__section_name__", &config_section);
		if (DM_STRLEN(config_section) != 0) {
			conf_sec = get_config_section_from_dmmap_section_name(config_section);
		}

		if (conf_sec == NULL) {
			dmuci_delete_by_section(new_s, NULL, NULL);
		}
	}
}

static void dmmap_synchronize_ddns(struct dmctx *dmctx)
{
	bool commit_uci = false;

	bbfdm_create_empty_file("/etc/bbfdm/dmmap/DynamicDNS");

	dmmap_sync_ddnsmngr(dmctx, "server");
	dmmap_sync_ddnsmngr(dmctx, "client");
	cleanup_dangling_hosts(&commit_uci);

	dmuci_commit_package_bbfdm("DynamicDNS");
	if (commit_uci == true) {
		dmuci_commit_package("ddnsmngr");
	}
}

static int init_ddnsmngr_module(void *data)
{
	struct dmctx bbf_ctx = {0};

	bbf_ctx_init(&bbf_ctx, NULL);
	dmmap_synchronize_ddns(&bbf_ctx);
	bbf_ctx_clean(&bbf_ctx);

	return 0;
}

static int sync_ddnsmngr_module(void *data)
{
	struct bbfdm_context *bbfdm_ctx = (struct bbfdm_context *)data;
	if (bbfdm_ctx == NULL)
		return 0;

	if (DM_STRCMP(bbfdm_ctx->uci_change_proto, "external") == 0) {
		BBF_INFO("Syncing ddnsmngr configuration");
		init_ddnsmngr_module(data);
	}

	return 0;
}

static bool service_exists(const char *service)
{
	struct uci_section *s = NULL;
	bool exist = false;

	uci_foreach_sections("ddnsmngr", "server", s) {
		char *service_name = NULL;

		dmuci_get_value_by_section_string(s, "service", &service_name);
		if (DM_STRCMP(service_name, service) == 0) {
			exist = true;
			break;
		}
	}

	return exist;
}

/*************************************************************
* ENTRY METHOD
*************************************************************/
static int browseDynamicDNSClientInst(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
	return generic_browse(dmctx, parent_node, prev_data, prev_instance);
}

static int browseDynamicDNSServerInst(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
	return generic_browse(dmctx, parent_node, prev_data, prev_instance);
}

static int browseDynamicDNSClientHostnameInst(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
	return generic_browse(dmctx, parent_node, prev_data, prev_instance);
}

/*************************************************************
* ADD & DEL OBJ
*************************************************************/
static int addObjDynamicDNSClient(char *refparam, struct dmctx *ctx, void *data, char **instance)
{
	struct dm_data *curr_data = (struct dm_data *)ctx->addobj_instance;
	char s_name[32] = {0};

	snprintf(s_name, sizeof(s_name), "client_%s", *instance);

	dmuci_add_named_section("ddnsmngr", "client", s_name, &curr_data->config_section);
	dmuci_set_value_by_section(curr_data->config_section, "enabled", "0");

	return 0;
}

static int delObjDynamicDNSClient(char *refparam, struct dmctx *ctx, void *data, char *instance, unsigned char del_action)
{
	struct uci_section *s = NULL, *stmp = NULL;

	// Remove all hosts configured for this client
	char *client = section_name(((struct dm_data *)data)->config_section);
	uci_foreach_sections_safe("ddnsmngr", "host", stmp, s) {
		char *value = NULL;

		dmuci_get_value_by_section_string(s, "dm_parent", &value);
		if (DM_STRCMP(value, client) == 0) {
			struct uci_section *dmmap_section = NULL;
			char sec_name[128] = {0};

			snprintf(sec_name, sizeof(sec_name), "%s.%s", section_config(s), section_name(s));
			get_dmmap_section_of_config_section("DynamicDNS", "Host", sec_name, &dmmap_section);

			dmuci_delete_by_section(s, NULL, NULL);
			dmuci_delete_by_section(dmmap_section, NULL, NULL);
		}
	}

	// Remove the client
	dmuci_delete_by_section(((struct dm_data *)data)->config_section, NULL, NULL);
	dmuci_delete_by_section(((struct dm_data *)data)->dmmap_section, NULL, NULL);
	return 0;
}

static int addObjDynamicDNSServer(char *refparam, struct dmctx *ctx, void *data, char **instance)
{
	struct dm_data *curr_data = (struct dm_data *)ctx->addobj_instance;
	char s_name[32] = {0};

	snprintf(s_name, sizeof(s_name), "server_%s", *instance);

	dmuci_add_named_section("ddnsmngr", "server", s_name, &curr_data->config_section);
	dmuci_set_value_by_section(curr_data->config_section, "enabled", "0");

	return 0;
}

static int delObjDynamicDNSServer(char *refparam, struct dmctx *ctx, void *data, char *instance, unsigned char del_action)
{
	struct uci_section *s = NULL;
	char *service_name = NULL;

	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "service", &service_name);
	dmuci_delete_by_section(((struct dm_data *)data)->config_section, NULL, NULL);
	dmuci_delete_by_section(((struct dm_data *)data)->dmmap_section, NULL, NULL);

	if (DM_STRLEN(service_name) == 0)
		return 0;

	// Now need to remove the service if configured to any client
	uci_foreach_sections("ddnsmngr", "client", s) {
		char *client_service = NULL;

		dmuci_get_value_by_section_string(s, "service_name", &client_service);
		if (DM_STRCMP(service_name, client_service) == 0)
			dmuci_set_value_by_section(s, "service_name", "");
	}

	return 0;
}

static int addObjDynamicDNSClientHostname(char *refparam, struct dmctx *ctx, void *data, char **instance)
{
	struct dm_data *curr_data = (struct dm_data *)ctx->addobj_instance;
	char s_name[64] = {0};

	char *client_s = section_name(((struct dm_data *)data)->config_section);
	if (DM_STRLEN(client_s) == 0)
		return FAULT_9002;

	snprintf(s_name, sizeof(s_name), "%s_host_%s", client_s, *instance);

	dmuci_add_named_section("ddnsmngr", "host", s_name, &curr_data->config_section);
	dmuci_set_value_by_section(curr_data->config_section, "enabled", "0");
	dmuci_set_value_by_section(curr_data->config_section, "dm_parent", client_s);

	return 0;
}

static int delObjDynamicDNSClientHostname(char *refparam, struct dmctx *ctx, void *data, char *instance, unsigned char del_action)
{
	dmuci_delete_by_section(((struct dm_data *)data)->config_section, NULL, NULL);
	dmuci_delete_by_section(((struct dm_data *)data)->dmmap_section, NULL, NULL);
	return 0;
}

/*************************************************************
* GET & SET PARAM
*************************************************************/
static int get_DynamicDNS_ClientNumberOfEntries(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	int cnt = get_number_of_entries(ctx, data, instance, NULL);
	dmasprintf(value, "%d", cnt);
	return 0;
}

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

static int get_DynamicDNS_SupportedServices(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	dmuci_get_option_value_string("ddnsmngr", "global", "supported_services", value);
	return 0;
}

static int get_DynamicDNSClient_Enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = dmuci_get_value_by_section_fallback_def(((struct dm_data *)data)->config_section, "enabled", "0");
	return 0;
}

static int set_DynamicDNSClient_Enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	bool b;

	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_boolean(ctx, value))
				return FAULT_9007;
			break;
		case VALUESET:
			string_to_bool(value, &b);
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "enabled", b ? "1" : "0");
			break;
	}
	return 0;
}

static int get_DynamicDNSClient_Status(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	json_object *res = NULL;

	char *enable = dmuci_get_value_by_section_fallback_def(((struct dm_data *)data)->config_section, "enabled", "0");
	if (DM_STRCMP(enable, "0") == 0) {
		*value = dmstrdup("Disabled");
		return 0;
	}

	char *client_sec = section_name(((struct dm_data *)data)->config_section);
	*value = dmstrdup("Error");

	dmubus_call("ddnsmngr", "client_status", UBUS_ARGS{{"client", client_sec, String}}, 1, &res);
	if (res != NULL) {
		*value = dmjson_get_value(res, 1, "status");
	}

	return 0;
}

static int get_DynamicDNSClient_LastError(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	json_object *res = NULL;

	*value = dmstrdup("NO_ERROR");

	char *enable = dmuci_get_value_by_section_fallback_def(((struct dm_data *)data)->config_section, "enabled", "0");
	if (DM_STRCMP(enable, "0") == 0) {
		return 0;
	}

	char *client_sec = section_name(((struct dm_data *)data)->config_section);
	dmubus_call("ddnsmngr", "last_error", UBUS_ARGS{{"client", client_sec, String}}, 1, &res);
	if (res != NULL) {
		*value = dmjson_get_value(res, 1, "last_error");
	}

	return 0;
}

static int get_DynamicDNSClient_Alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return bbf_get_alias(ctx, ((struct dm_data *)data)->dmmap_section, "Alias", instance, value);
}

static int set_DynamicDNSClient_Alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	return bbf_set_alias(ctx, ((struct dm_data *)data)->dmmap_section, "Alias", instance, value);
}

static int get_DynamicDNSClient_Server(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *service_name = NULL;

	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "service_name", &service_name);
	if (DM_STRLEN(service_name) == 0)
		return 0;

	return _bbfdm_get_references(ctx, "Device.DynamicDNS.Server.", "ServiceName", service_name, value);
}

static int set_DynamicDNSClient_Server(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	char *allowed_objects[] = {"Device.DynamicDNS.Server.", NULL};
	struct dm_reference reference = {0};

	bbfdm_get_reference_linker(ctx, value, &reference);

	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, reference.path, -1, 256, NULL, NULL))
				return FAULT_9007;

			if (dm_validate_allowed_objects(ctx, &reference, allowed_objects))
				return FAULT_9007;

			if (DM_STRLEN(reference.value) == 0) {
				bbfdm_set_fault_message(ctx, "This Server object has no ServiceName.");
				return FAULT_9001;
			}

			break;
		case VALUESET:
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "service_name", reference.value);
			break;
	}
	return 0;
}

static int get_DynamicDNSClient_Interface(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *interface = NULL;

	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "interface", &interface);
	return _bbfdm_get_references(ctx, "Device.IP.Interface.", "Name", interface, value);
}

static int set_DynamicDNSClient_Interface(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	char *allowed_objects[] = {"Device.IP.Interface.", NULL};
	struct dm_reference reference = {0};

	bbfdm_get_reference_linker(ctx, value, &reference);

	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, reference.path, -1, 256, NULL, NULL))
				return FAULT_9007;

			if (dm_validate_allowed_objects(ctx, &reference, allowed_objects))
				return FAULT_9007;

			break;
		case VALUESET:
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "interface", reference.value);
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "ip_network", reference.value);
			break;
	}
	return 0;
}

static int get_DynamicDNSClient_Username(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "username", value);
	return 0;
}

static int set_DynamicDNSClient_Username(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, value, -1, 256, NULL, NULL))
				return FAULT_9007;
			break;
		case VALUESET:
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "username", value);
			break;
	}
	return 0;
}

static int get_DynamicDNSClient_Password(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "password", value);
	return 0;
}

static int set_DynamicDNSClient_Password(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, value, -1, 256, NULL, NULL))
				return FAULT_9007;
			break;
		case VALUESET:
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "password", value);
			break;
	}
	return 0;
}

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

#ifdef DDNSMNGR_DDNSSCRIPT_BACKEND
/* IPVersion is stored in use_ipv6: "1" -> 6 (AAAA), "0" -> 4 (A), unset -> -1
 * (no preference, falls back to IPv4 - it does NOT update both records). */
static int get_DynamicDNSClient_IPVersion(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *v = NULL;

	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "use_ipv6", &v);
	if (DM_STRCMP(v, "1") == 0)
		*value = dmstrdup("6");
	else if (DM_STRCMP(v, "0") == 0)
		*value = dmstrdup("4");
	else
		*value = dmstrdup("-1");

	return 0;
}

static int set_DynamicDNSClient_IPVersion(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action) {
		case VALUECHECK:
			if (DM_STRCMP(value, "4") != 0 && DM_STRCMP(value, "6") != 0 && DM_STRCMP(value, "-1") != 0)
				return FAULT_9007;
			break;
		case VALUESET:
			if (DM_STRCMP(value, "6") == 0)
				dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "use_ipv6", "1");
			else if (DM_STRCMP(value, "4") == 0)
				dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "use_ipv6", "0");
			else
				dmuci_delete_by_section(((struct dm_data *)data)->config_section, "use_ipv6", NULL);
			break;
	}
	return 0;
}
#endif /* DDNSMNGR_DDNSSCRIPT_BACKEND */

static int get_DynamicDNSClientHostname_Enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = dmuci_get_value_by_section_fallback_def(((struct dm_data *)data)->config_section, "enabled", "0");
	return 0;
}

static int set_DynamicDNSClientHostname_Enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	bool b;

	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_boolean(ctx, value))
				return FAULT_9007;
			break;
		case VALUESET:
			string_to_bool(value, &b);
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "enabled", b ? "1" : "0");
			break;
	}
	return 0;
}

static int get_DynamicDNSClientHostname_Status(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	json_object *res = NULL;

	char *enable = dmuci_get_value_by_section_fallback_def(((struct dm_data *)data)->config_section, "enabled", "0");
	if (DM_STRCMP(enable, "0") == 0) {
		*value = dmstrdup("Disabled");
		return 0;
	}

	char *host_sec = section_name(((struct dm_data *)data)->config_section);
	*value = dmstrdup("Error");

	dmubus_call("ddnsmngr", "host_status", UBUS_ARGS{{"host", host_sec, String}}, 1, &res);
	if (res != NULL) {
		*value = dmjson_get_value(res, 1, "status");
	}

	return 0;
}

static int get_DynamicDNSClientHostname_Name(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "lookup_host", value);
	return 0;
}

static int set_DynamicDNSClientHostname_Name(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, value, -1, 256, NULL, NULL))
				return FAULT_9007;
			break;
		case VALUESET:
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "lookup_host", value);
			break;
	}
	return 0;
}

static int get_DynamicDNSClientHostname_LastUpdate(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	json_object *res = NULL;

	char *host_sec = section_name(((struct dm_data *)data)->config_section);
	*value = dmstrdup("0001-01-01T00:00:00Z");

	dmubus_call("ddnsmngr", "last_update", UBUS_ARGS{{"host", host_sec, String}}, 1, &res);
	if (res != NULL) {
		*value = dmjson_get_value(res, 1, "last_update");
	}

	return 0;
}

static int get_DynamicDNSServer_Enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = dmuci_get_value_by_section_fallback_def(((struct dm_data *)data)->config_section, "enabled", "0");
	return 0;
}

static int set_DynamicDNSServer_Enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	bool b;

	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_boolean(ctx, value))
				return FAULT_9007;
			break;
		case VALUESET:
			string_to_bool(value, &b);
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "enabled", b ? "1" : "0");
			break;
	}
	return 0;
}

static int get_DynamicDNSServer_Name(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "name", value);
	return 0;
}

static int set_DynamicDNSServer_Name(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, value, -1, 64, NULL, NULL))
				return FAULT_9007;
			break;
		case VALUESET:
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "name", value);
			break;
	}
	return 0;
}

static int get_DynamicDNSServer_Alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return bbf_get_alias(ctx, ((struct dm_data *)data)->dmmap_section, "Alias", instance, value);
}

static int set_DynamicDNSServer_Alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)

{
	return bbf_set_alias(ctx, ((struct dm_data *)data)->dmmap_section, "Alias", instance, value);
}

static int get_DynamicDNSServer_ServiceName(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "service", value);
	return 0;
}

static int set_DynamicDNSServer_ServiceName(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	char file_path[128] = {0};
	char *curr_service = NULL;
	char *client_service = NULL;
	struct uci_section *s = NULL;

	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, value, -1, 256, NULL, NULL))
				return FAULT_9007;

			if (DM_STRLEN(value) == 0)
				break;

			// Value must be a member of SupportedServices list
			snprintf(file_path, sizeof(file_path), "/%s/%s.json", DDNS_SERVICES_DEFAULT, value);
			if (!file_exists(file_path))
				return FAULT_9007;

			// Value must not be configured with other instance since its a unique paramete
			dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "service", &curr_service);
			if (DM_STRCMP(curr_service, value) == 0)
				break; // instance with same existing value should allow

			if (service_exists(value)) {
				bbfdm_set_fault_message(ctx, "ServiceName already exist.");
				return FAULT_9001;
			}

			break;
		case VALUESET:
			dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "service", &curr_service);
			if (DM_STRCMP(curr_service, value) == 0) // Same value as existing
				break;

			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "service", value);
			if (DM_STRLEN(curr_service) == 0)
				break;

			// Now need to modify all clients configured with older service name
			uci_foreach_sections("ddnsmngr", "client", s) {
				dmuci_get_value_by_section_string(s, "service_name", &client_service);
				if (DM_STRCMP(curr_service, client_service) == 0) {
					dmuci_set_value_by_section(s, "service_name", value);
				}
			}
			break;
	}
	return 0;
}

/* Whether the server's service file uses an https ipv4 url. This is only a
 * scheme check (not URL parsing); the host/port parser lives in
 * ddns_url_lib.sh and is reached via the "server_info" ubus method. */
static bool ddns_service_is_https(struct uci_section *s)
{
	char *service = NULL;
	char file_path[128] = {0};
	bool https = false;

	dmuci_get_value_by_section_string(s, "service", &service);
	if (DM_STRLEN(service) == 0)
		return false;

	snprintf(file_path, sizeof(file_path), "%s/%s.json", DDNS_SERVICES_DEFAULT, service);

	json_object *json = json_object_from_file(file_path);
	if (!json)
		return false;

	https = (DM_STRNCMP(dmjson_get_value(json, 2, "ipv4", "url"), "https", 5) == 0);
	json_object_put(json);

	return https;
}

static int get_DynamicDNSServer_SupportedProtocols(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = dmstrdup(ddns_service_is_https(((struct dm_data *)data)->config_section) ? "HTTPS" : "HTTP,HTTPS");
	return 0;
}

static int get_DynamicDNSServer_Protocol(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *proto = NULL;

	/* explicit override wins, otherwise derive from the service file */
	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "protocol", &proto);
	if (DM_STRCMP(proto, "https") == 0)
		*value = dmstrdup("HTTPS");
	else if (DM_STRCMP(proto, "http") == 0)
		*value = dmstrdup("HTTP");
	else
		*value = dmstrdup(ddns_service_is_https(((struct dm_data *)data)->config_section) ? "HTTPS" : "HTTP");

	return 0;
}

static int set_DynamicDNSServer_Protocol(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	char *SupportedProtocols[] = {"HTTP", "HTTPS", NULL};

	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, value, -1, -1, SupportedProtocols, NULL))
				return FAULT_9007;
			break;
		case VALUESET:
#ifdef DDNSMNGR_DDNSSCRIPT_BACKEND
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "protocol",
					DM_STRCMP(value, "HTTPS") == 0 ? "https" : "http");
#endif
			break;
	}
	return 0;
}

#ifdef DDNSMNGR_DDNSSCRIPT_BACKEND
/* Host/port are derived by the "server_info" ubus method, which parses the
 * service file URL using ddns_url_lib.sh (the single URL parser). */
static int get_DynamicDNSServer_ServerAddress(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *addr = NULL, *service = NULL;
	json_object *res = NULL;

	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "server_address", &addr);
	if (DM_STRLEN(addr)) {
		*value = dmstrdup(addr);
		return 0;
	}

	*value = dmstrdup("");
	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "service", &service);
	if (DM_STRLEN(service) == 0)
		return 0;

	dmubus_call("ddnsmngr", "server_info", UBUS_ARGS{{"service", service, String}}, 1, &res);
	if (res) {
		char *addr = dmjson_get_value(res, 1, "address");
		if (DM_STRLEN(addr))
			*value = addr;
	}

	return 0;
}

static int set_DynamicDNSServer_ServerAddress(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_string(ctx, value, -1, 256, NULL, NULL))
				return FAULT_9007;
			break;
		case VALUESET:
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "server_address", value);
			break;
	}
	return 0;
}

static int get_DynamicDNSServer_ServerPort(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *p = NULL, *service = NULL;
	json_object *res = NULL;

	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "server_port", &p);
	if (DM_STRLEN(p)) {
		*value = dmstrdup(p);
		return 0;
	}

	*value = dmstrdup("0");
	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "service", &service);
	if (DM_STRLEN(service) == 0)
		return 0;

	dmubus_call("ddnsmngr", "server_info", UBUS_ARGS{{"service", service, String}}, 1, &res);
	if (res) {
		char *p = dmjson_get_value(res, 1, "port");
		if (DM_STRLEN(p))
			*value = p;
	}

	return 0;
}

static int set_DynamicDNSServer_ServerPort(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{"1","65535"}}, 1))
				return FAULT_9007;
			break;
		case VALUESET:
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "server_port", value);
			break;
	}
	return 0;
}

static int get_DynamicDNSServer_CheckInterval(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *v = NULL;

	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "check_interval", &v);
	*value = DM_STRLEN(v) ? dmstrdup(v) : dmstrdup("600");
	return 0;
}

static int set_DynamicDNSServer_CheckInterval(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{NULL,NULL}}, 1))
				return FAULT_9007;
			break;
		case VALUESET:
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "check_interval", value);
			break;
	}
	return 0;
}

static int get_DynamicDNSServer_RetryInterval(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *v = NULL;

	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "retry_interval", &v);
	*value = DM_STRLEN(v) ? dmstrdup(v) : dmstrdup("600");
	return 0;
}

static int set_DynamicDNSServer_RetryInterval(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{NULL,NULL}}, 1))
				return FAULT_9007;
			break;
		case VALUESET:
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "retry_interval", value);
			break;
	}
	return 0;
}

static int get_DynamicDNSServer_MaxRetries(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *v = NULL;

	dmuci_get_value_by_section_string(((struct dm_data *)data)->config_section, "max_retries", &v);
	if (DM_STRLEN(v)) {
		*value = dmstrdup(v);
		return 0;
	}

	dmuci_get_option_value_string("ddnsmngr", "global", "default_max_retries", &v);
	*value = DM_STRLEN(v) ? dmstrdup(v) : dmstrdup("100");
	return 0;
}

static int set_DynamicDNSServer_MaxRetries(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	switch (action)	{
		case VALUECHECK:
			if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{NULL,NULL}}, 1))
				return FAULT_9007;
			break;
		case VALUESET:
			dmuci_set_value_by_section(((struct dm_data *)data)->config_section, "max_retries", value);
			break;
	}
	return 0;
}
#endif /* DDNSMNGR_DDNSSCRIPT_BACKEND */

/**********************************************************************************************************************************
*                                            OBJ & LEAF DEFINITION
***********************************************************************************************************************************/
/*** Device. ***/
DMOBJ tDeviceObjs[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type, uniqueKeys*/
{"DynamicDNS", &DMREAD, NULL, NULL, NULL, NULL, NULL, NULL, tDynamicDNSObjs, tDynamicDNSParams, NULL, BBFDM_BOTH, NULL},
{0}
};

/* *** Device.DynamicDNS. *** */
DMOBJ tDynamicDNSObjs[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type, uniqueKeys, version*/
{"Client", &DMWRITE, addObjDynamicDNSClient, delObjDynamicDNSClient, NULL, browseDynamicDNSClientInst, NULL, NULL, tDynamicDNSClientObj, tDynamicDNSClientParams, NULL, BBFDM_BOTH, NULL},
{"Server", &DMWRITE, addObjDynamicDNSServer, delObjDynamicDNSServer, NULL, browseDynamicDNSServerInst, NULL, NULL, NULL, tDynamicDNSServerParams, NULL, BBFDM_BOTH, NULL},
{0}
};

DMLEAF tDynamicDNSParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
{"ClientNumberOfEntries", &DMREAD, DMT_UNINT, get_DynamicDNS_ClientNumberOfEntries, NULL, BBFDM_BOTH},
{"ServerNumberOfEntries", &DMREAD, DMT_UNINT, get_DynamicDNS_ServerNumberOfEntries, NULL, BBFDM_BOTH},
{"SupportedServices", &DMREAD, DMT_STRING, get_DynamicDNS_SupportedServices, NULL, BBFDM_BOTH},
{0}
};

/* *** Device.DynamicDNS.Client.{i}. *** */
DMOBJ tDynamicDNSClientObj[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type, uniqueKeys, version*/
{"Hostname", &DMWRITE, addObjDynamicDNSClientHostname, delObjDynamicDNSClientHostname, NULL, browseDynamicDNSClientHostnameInst, NULL, NULL, NULL, tDynamicDNSClientHostnameParams, NULL, BBFDM_BOTH, NULL},
{0}
};

DMLEAF tDynamicDNSClientParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
{"Enable", &DMWRITE, DMT_BOOL, get_DynamicDNSClient_Enable, set_DynamicDNSClient_Enable, BBFDM_BOTH},
{"Status", &DMREAD, DMT_STRING, get_DynamicDNSClient_Status, NULL, BBFDM_BOTH},
{"Alias", &DMWRITE, DMT_STRING, get_DynamicDNSClient_Alias, set_DynamicDNSClient_Alias, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"LastError", &DMREAD, DMT_STRING, get_DynamicDNSClient_LastError, NULL, BBFDM_BOTH},
{"Server", &DMWRITE, DMT_STRING, get_DynamicDNSClient_Server, set_DynamicDNSClient_Server, BBFDM_BOTH, DM_FLAG_UNIQUE|DM_FLAG_REFERENCE},
{"Interface", &DMWRITE, DMT_STRING, get_DynamicDNSClient_Interface, set_DynamicDNSClient_Interface, BBFDM_BOTH, DM_FLAG_REFERENCE},
{"Username", &DMWRITE, DMT_STRING, get_DynamicDNSClient_Username, set_DynamicDNSClient_Username, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"Password", &DMWRITE, DMT_STRING, get_DynamicDNSClient_Password, set_DynamicDNSClient_Password, BBFDM_BOTH, DM_FLAG_SECURE},
#ifdef DDNSMNGR_DDNSSCRIPT_BACKEND
{"IPVersion", &DMWRITE, DMT_INT, get_DynamicDNSClient_IPVersion, set_DynamicDNSClient_IPVersion, BBFDM_BOTH},
#endif
{"HostnameNumberOfEntries", &DMREAD, DMT_UNINT, get_DynamicDNSClient_HostnameNumberOfEntries, NULL, BBFDM_BOTH},
{0}
};

/* *** Device.DynamicDNS.Client.{i}.Hostname.{i}. *** */
DMLEAF tDynamicDNSClientHostnameParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
{"Enable", &DMWRITE, DMT_BOOL, get_DynamicDNSClientHostname_Enable, set_DynamicDNSClientHostname_Enable, BBFDM_BOTH},
{"Status", &DMREAD, DMT_STRING, get_DynamicDNSClientHostname_Status, NULL, BBFDM_BOTH},
{"Name", &DMWRITE, DMT_STRING, get_DynamicDNSClientHostname_Name, set_DynamicDNSClientHostname_Name, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"LastUpdate", &DMREAD, DMT_TIME, get_DynamicDNSClientHostname_LastUpdate, NULL, BBFDM_BOTH},
{0}
};

/* *** Device.DynamicDNS.Server.{i}. *** */
DMLEAF tDynamicDNSServerParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
{"Enable", &DMWRITE, DMT_BOOL, get_DynamicDNSServer_Enable, set_DynamicDNSServer_Enable, BBFDM_BOTH},
{"Name", &DMWRITE, DMT_STRING, get_DynamicDNSServer_Name, set_DynamicDNSServer_Name, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"Alias", &DMWRITE, DMT_STRING, get_DynamicDNSServer_Alias, set_DynamicDNSServer_Alias, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"ServiceName", &DMWRITE, DMT_STRING, get_DynamicDNSServer_ServiceName, set_DynamicDNSServer_ServiceName, BBFDM_BOTH, DM_FLAG_UNIQUE|DM_FLAG_LINKER},
{"SupportedProtocols", &DMREAD, DMT_STRING, get_DynamicDNSServer_SupportedProtocols, NULL, BBFDM_BOTH},
{"Protocol", &DMWRITE, DMT_STRING, get_DynamicDNSServer_Protocol, set_DynamicDNSServer_Protocol, BBFDM_BOTH},
#ifdef DDNSMNGR_DDNSSCRIPT_BACKEND
{"ServerAddress", &DMWRITE, DMT_STRING, get_DynamicDNSServer_ServerAddress, set_DynamicDNSServer_ServerAddress, BBFDM_BOTH},
{"ServerPort", &DMWRITE, DMT_UNINT, get_DynamicDNSServer_ServerPort, set_DynamicDNSServer_ServerPort, BBFDM_BOTH},
{"CheckInterval", &DMWRITE, DMT_UNINT, get_DynamicDNSServer_CheckInterval, set_DynamicDNSServer_CheckInterval, BBFDM_BOTH},
{"RetryInterval", &DMWRITE, DMT_UNINT, get_DynamicDNSServer_RetryInterval, set_DynamicDNSServer_RetryInterval, BBFDM_BOTH},
{"MaxRetries", &DMWRITE, DMT_UNINT, get_DynamicDNSServer_MaxRetries, set_DynamicDNSServer_MaxRetries, BBFDM_BOTH},
#endif
{0}
};
