/*
 * 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 <libgen.h>
#include <libbbfdm_api.h>
#include <libbbfdm-api/dmcommon.h>

#include "common.h"

static void send_transfer_complete_event(struct ubus_context *ubus_ctx, const char *command, const char *obj_path, const char *transfer_url,
	char *fault_string, time_t start_t, time_t complete_t,const char *commandKey, const char *transfer_type, const char *reqstr)
{
	char start_time[32] = {0};
	char complete_time[32] = {0};
	struct blob_buf bb;

	strftime(start_time, sizeof(start_time), "%Y-%m-%dT%H:%M:%SZ", gmtime(&start_t));
	strftime(complete_time, sizeof(complete_time), "%Y-%m-%dT%H:%M:%SZ", gmtime(&complete_t));

	memset(&bb, 0, sizeof(struct blob_buf));
	blob_buf_init(&bb, 0);

	blobmsg_add_string(&bb, "name", "Device.LocalAgent.TransferComplete!");
	void *arr = blobmsg_open_array(&bb, "input");

	fill_blob_param(&bb, "Command", command, DMT_TYPE[DMT_STRING], 0);
	fill_blob_param(&bb, "CommandKey", commandKey ? commandKey : "", DMT_TYPE[DMT_STRING], 0);
	fill_blob_param(&bb, "Requestor", reqstr ? reqstr : "", DMT_TYPE[DMT_STRING], 0);
	fill_blob_param(&bb, "TransferType", transfer_type, DMT_TYPE[DMT_STRING], 0);
	fill_blob_param(&bb, "Affected", obj_path, DMT_TYPE[DMT_STRING], 0);
	fill_blob_param(&bb, "TransferURL", transfer_url, DMT_TYPE[DMT_STRING], 0);
	fill_blob_param(&bb, "StartTime", start_time, DMT_TYPE[DMT_STRING], 0);
	fill_blob_param(&bb, "CompleteTime", complete_time, DMT_TYPE[DMT_STRING], 0);

	if (DM_STRLEN(fault_string) == 0) {
		fill_blob_param(&bb, "FaultCode", "0", DMT_TYPE[DMT_STRING], 0);
	} else {
		fill_blob_param(&bb, "FaultCode", "7000", DMT_TYPE[DMT_STRING], 0);
	}

	fill_blob_param(&bb, "FaultString", fault_string, DMT_TYPE[DMT_STRING], 0);
	blobmsg_close_array(&bb, arr);

	ubus_send_event(ubus_ctx, "bbfdm.event", bb.head);

	blob_buf_free(&bb);
}

static bool get_response_code_status(const char *url, int response_code)
{
	if ((strncmp(url, HTTP_URI, strlen(HTTP_URI)) == 0 && response_code != 201) ||
		(strncmp(url, FTP_URI, strlen(FTP_URI)) == 0 && response_code != 226) ||
		(strncmp(url, FILE_URI, strlen(FILE_URI)) == 0 && response_code != 0) ||
		(strncmp(url, HTTP_URI, strlen(HTTP_URI)) && strncmp(url, FTP_URI, strlen(FTP_URI)) && strncmp(url, FILE_URI, strlen(FILE_URI)))) {
		return false;
	}

	return true;
}

static int bbf_upload_log(struct ubus_context *ubus_ctx, const char *url, const char *username, const char *password,
                char *config_name, const char *command, const char *obj_path, int type, const char *cmdkey, const char *reqstr)
{
	int res = 0;
	char fault_msg[128] = {0};

	// Upload the config file
	time_t start_time = time(NULL);
	long res_code = upload_file(config_name, url, username, password);
	time_t complete_time = time(NULL);

	// Check if the upload operation was successful
	if (!get_response_code_status(url, res_code)) {
		snprintf(fault_msg, sizeof(fault_msg), "Upload operation is failed, fault code (%ld)", res_code);
		res = -1;
	}

	// Send the transfer complete event
	send_transfer_complete_event(ubus_ctx, command, obj_path, url, fault_msg, start_time, complete_time, cmdkey, "Upload", reqstr);

	return res;
}

static int synchronize_logfiles_with_dmmap(void)
{
	int i;
	struct uci_section *s = NULL, *stmp = NULL;
	char *file_name = NULL;

	for (i = 0; vendor_log_dir[i] != NULL; i++) {
		DIR *dir;
		struct dirent *d_file;

		if ((dir = opendir(vendor_log_dir[i])) == NULL)
			continue;

		while ((d_file = readdir(dir)) != NULL) {
			if (DM_STRLEN(d_file->d_name) == 0)
				continue;

			if (DM_STRCMP(d_file->d_name, ".") == 0 || DM_STRCMP(d_file->d_name, "..") == 0)
				continue;

			if (DM_STRSTR(d_file->d_name, "_logrotate") != NULL)
				continue;

			if (d_file->d_type != DT_REG)
				continue;

			char tmp_filename[5010] = {0};
			snprintf(tmp_filename, sizeof(tmp_filename), "file://%s/%s", vendor_log_dir[i], d_file->d_name);

			if (!is_dmmap_section_exist_eq("dmmap", "vendorlog", "log_file", tmp_filename)) {
				dmuci_add_section_bbfdm("dmmap", "vendorlog", &s);
				dmuci_set_value_by_section(s, "log_file", tmp_filename);
			}
		}

		if (dir) {
			closedir(dir);
		}
	}

	s = NULL;
	uci_path_foreach_sections_safe(bbfdm, "dmmap", "vendorlog", stmp, s) {
		dmuci_get_value_by_section_string(s, "log_file", &file_name);

		for (i = 0; vendor_log_dir[i] != NULL; i++) {
			if (DM_STRNCMP(file_name+7, vendor_log_dir[i], strlen(vendor_log_dir[i])) != 0)
				continue;

			if (file_exists(file_name+7) == false)
				dmuci_delete_by_section_bbfdm(s, NULL, NULL);
		}
	}

	return 0;
}

static int browseVlfInst(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
	struct dm_data curr_data = {0};
	char *inst = NULL;
	struct uci_section *s = NULL;

	synchronize_logfiles_with_dmmap();
	uci_path_foreach_sections(bbfdm, "dmmap", "vendorlog", s) {
		curr_data.dmmap_section = s;
		inst = handle_instance(dmctx, parent_node, s, "vlf_instance", "vlf_alias");

		if (DM_LINK_INST_OBJ(dmctx, parent_node, (void *)&curr_data, inst) == DM_STOP)
			break;
	}
	return 0;
}

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

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

static int set_vlf_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, "vlf_alias", instance, value);
}

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

static int get_vlf_max_size(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = dmstrdup("0");
	return 0;
}

static int get_vlf_persistent(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *name = NULL;
	dmuci_get_value_by_section_string(((struct dm_data *)data)->dmmap_section, "log_file", &name);

	if (DM_STRNCMP(name, "file:///log", 11) == 0)
		*value = dmstrdup("1");
	else
		*value = dmstrdup("0");

	return 0;
}

/*************************************************************
 * OPERATE COMMANDS
 *************************************************************/
static operation_args vendor_log_file_upload_args = {
    .in = (const char *[]) {
        "URL",
        "Username",
        "Password",
        NULL
    }
};

static int get_operate_args_DeviceInfoVendorLogFile_Upload(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = (char *)&vendor_log_file_upload_args;
	return 0;
}

static int archive_log_file(char *arch_file, char *file_path)
{
	DIR *dir;
	struct dirent *d_file;
	bool rotatefile_exist = false;

	int arch_file_len = DM_STRLEN(arch_file);
	int file_path_len = DM_STRLEN(file_path);

	if (arch_file_len == 0 || file_path_len == 0)
		return -1;

	char tmp_path1[file_path_len + 1];
	char tmp_path2[file_path_len + 1];

	snprintf(tmp_path1, sizeof(tmp_path1), "%s", file_path);
	snprintf(tmp_path2, sizeof(tmp_path2), "%s", file_path);

	char *dir_name = dirname(tmp_path1);
	if (DM_STRLEN(dir_name) == 0)
		return -1;

	char *file_name = basename(tmp_path2);
	if (DM_STRLEN(file_name) == 0)
		return -1;

	int cmd_len = arch_file_len + (file_path_len * 2) + 25;
	char cmd[cmd_len];

	memset(cmd, 0, cmd_len);

	if ((dir = opendir(dir_name)) == NULL)
		return -1;

	while ((d_file = readdir(dir)) != NULL) {
		if (DM_STRLEN(d_file->d_name) == 0)
			continue;

		if (DM_STRCMP(d_file->d_name, ".") == 0 || DM_STRCMP(d_file->d_name, "..") == 0)
			continue;

		if (d_file->d_type != DT_REG)
			continue;

		char regexp[1024] = {0};
		snprintf(regexp, sizeof(regexp), "^%s.[1-9][0-9]*_logrotate", file_name);

		if (match(d_file->d_name, regexp, 0, NULL) == true) {
			rotatefile_exist = true;
			break;
		}
	}

	if (dir) {
		closedir(dir);
	}

	if (rotatefile_exist) {
		snprintf(cmd, cmd_len, "tar -cf %s %s %s.*_logrotate*", arch_file, file_path, file_path);
	} else {
		snprintf(cmd, cmd_len, "tar -cf %s %s", arch_file, file_path);
	}

	FILE *pp = popen(cmd, "r"); // flawfinder: ignore
	if (pp) {
		int status = pclose(pp);
		if (WEXITSTATUS(status) != 0) {
			return -1;
		}
	} else {
		return -1;
	}

	return 0;
}

static int operate_DeviceInfoVendorLogFile_Upload(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	char upload_path[256] = {0};
	char upload_command[270] = {0};
	char log_tar_file[256] = {0};
	char *vlf_file_path = NULL;
	char *cmdkey = NULL, *reqstr = NULL;
	int res;

	DM_STRNCPY(log_tar_file, "/tmp/vendor_log-XXXXXX.tar", sizeof(log_tar_file));

	char *ret = DM_STRRCHR(refparam, '.');
	if (!ret)
		return USP_FAULT_INVALID_ARGUMENT;

	if ((ret - refparam + 2) < sizeof(upload_path)) {
		snprintf(upload_path, ret - refparam + 2, "%s", refparam);
	}

	if (ctx->dm_type == BBFDM_USP) {
		DM_STRNCPY(upload_command, refparam, sizeof(upload_command));
		cmdkey = dmjson_get_value((json_object *)value, 1, "__BBF_CommandKey");
		reqstr = dmjson_get_value((json_object *)value, 1, "__BBF_Requestor");
	}

	char *user = dmjson_get_value((json_object *)value, 1, "Username");
	char *pass = dmjson_get_value((json_object *)value, 1, "Password");
	char *url = dmjson_get_value((json_object *)value, 1, "URL");

	if (url[0] == '\0')
		return USP_FAULT_INVALID_ARGUMENT;

	res = mkstemps(log_tar_file, 4);
	if (res == -1) {
		return USP_FAULT_COMMAND_FAILURE;
	}
	close(res);

	dmuci_get_value_by_section_string(((struct dm_data *)data)->dmmap_section, "log_file", &vlf_file_path);

	if (DM_STRLEN(vlf_file_path) == 0) {
		res = USP_FAULT_COMMAND_FAILURE;
	} else {
		char *filepath = vlf_file_path + 7;

		res = archive_log_file(log_tar_file, filepath);
	}

	if (res == 0) {
		res = bbf_upload_log(ctx->ubus_ctx, url, user, pass, log_tar_file, upload_command, upload_path, ctx->dm_type, cmdkey, reqstr);
	}

	if (file_exists(log_tar_file))
		remove(log_tar_file);

	return res ? USP_FAULT_COMMAND_FAILURE : 0;
}

/**********************************************************************************************************************************
*                                            OBJ & LEAF DEFINITION
***********************************************************************************************************************************/
/* *** Device.DeviceInfo.VendorLogFile.{i}. *** */
DMLEAF tDeviceInfoVendorLogFileParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
{"Alias", &DMWRITE, DMT_STRING, get_vlf_alias, set_vlf_alias, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"Name", &DMREAD, DMT_STRING, get_vlf_name, NULL, BBFDM_BOTH, DM_FLAG_UNIQUE|DM_FLAG_LINKER},
{"MaximumSize", &DMREAD, DMT_UNINT, get_vlf_max_size, NULL, BBFDM_BOTH},
{"Persistent", &DMREAD, DMT_BOOL, get_vlf_persistent, NULL, BBFDM_BOTH},
{"Upload()", &DMASYNC, DMT_COMMAND, get_operate_args_DeviceInfoVendorLogFile_Upload, operate_DeviceInfoVendorLogFile_Upload, BBFDM_USP},
{0}
};

/* *** Device.DeviceInfo. *** */
DMOBJ tDeviceInfoVendorLogObj[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type, uniqueKeys, version*/
{"VendorLogFile", &DMREAD, NULL, NULL, NULL, browseVlfInst, NULL, NULL, NULL, tDeviceInfoVendorLogFileParams, NULL, BBFDM_BOTH, NULL},
{0}
};

DMLEAF tVendorLogNumberofEntries[] = {
{"VendorLogFileNumberOfEntries", &DMREAD, DMT_UNINT, get_DeviceInfo_VendorLogFileNumberOfEntries, NULL, BBFDM_BOTH},
{0}
};

/* ********** DynamicObj ********** */
DM_MAP_OBJ tDynamicObj[] = {
/* parentobj, nextobject, parameter */
{"Device.DeviceInfo.", tDeviceInfoVendorLogObj, tVendorLogNumberofEntries},
{0}
};
