#!/bin/sh

# shellcheck disable=SC1091
. /usr/share/libubox/jshn.sh

JSON_OUT=0
SPEC_DIR="/usr/share/self-diagnostics/spec"
SPEC_EXT_DIR="/etc/self-diagnostics/spec"
REPORT_PATH="/var/log"
REPORT_TEMP_DIR="$(mktemp -p ${REPORT_PATH} -d)"
REPORT_NAME="self-test-diagnostics"
VERBOSE=0
COMPOPTS=""
TIMEOUT=5
JSON_OUT_BUFFER=""

log()
{
	log_file="${REPORT_TEMP_DIR}/execution.log"
	if [ "$VERBOSE" -eq 1 ]; then
		logger -p debug -t "$0" "$*"
	fi
	echo "[$(date +%Y:%m:%d-%H:%M:%S)] $*" >> "${log_file}"
}

err_log()
{
	log_file="${REPORT_TEMP_DIR}/execution.log"
	logger -p err -t "$0" "$*"
	echo "[$(date +%Y:%m:%d-%H:%M:%S) ERR] $*" >> "${log_file}"
}

# shellcheck disable=SC3043
generate_report()
{
	local filename

	filename="${REPORT_PATH}/${REPORT_NAME}"

	[ -f "${filename}.tar" ] && rm "${filename}.tar"
	[ -f "${filename}.tar.gz" ] && rm "${filename}.tar.gz"

	log "# Report generation completed #"
	cd "${REPORT_TEMP_DIR}" && {
		filename="${filename}.tar"
		tar -cf "${filename}" ./*.log
	}

	if [ -n "$COMPOPTS" ]; then
		gzip -"${COMPOPTS}" -f "${filename}"
		filename="${filename}.gz"
	fi

	# Move logs if failed to generate tar
	if [ ! -f "${filename}" ]; then
		mv "${REPORT_TEMP_DIR}"/*.log "${REPORT_PATH}"/
	fi

	if [ "${JSON_OUT}" -eq 1 ]; then
		json_init
		json_load "${JSON_OUT_BUFFER}"
		if [ -f "${filename}" ]; then
			json_add_string result "${filename}"
		else
			log "error: Failed to generate report tar, check logs in ${REPORT_PATH}"
			json_add_string error "Failed to generate report tar, check logs in ${REPORT_PATH}"
		fi
		json_dump
	else
		if [ -f "${filename}" ]; then
			echo "result: ${filename}"
		else
			log "error: Failed to generate report tar, check logs in ${REPORT_PATH}"
			echo "error: Failed to generate report tar, check logs in ${REPORT_PATH}"
		fi
	fi
}

cleanup()
{
	if [ -d "${REPORT_TEMP_DIR}" ]; then
		generate_report
		rm -rf "${REPORT_TEMP_DIR}"
	fi
}

term_cleanup()
{
	if [ -d "${REPORT_TEMP_DIR}" ]; then
		err_log "Exiting due to TERM/INT signal"
		generate_report
		rm -rf "${REPORT_TEMP_DIR}"
	fi
}

trap cleanup EXIT
trap term_cleanup INT TERM

help()
{
	echo "Generate self diagnostics report"
	echo
	echo "Syntax: $0 [-m|h|l|j]"
	echo
	echo "Options:"
	echo "  l     List available module(s)"
	echo "  m     Generate system report of specific module(s)"
	echo "  j     Enable JSON output"
	echo "  h     Print this help"
	echo
}

# Alias ubus to have a smaller 5-second timeout on all subsequent calls
ubus()
{
	if [ "${1}" = "call" ]; then
		if command ubus list >/dev/null 2>&1; then
			command ubus "$@";
		fi
	else
		command ubus "$@";
	fi

}

# shellcheck disable=SC3043,SC3060,SC2034
config_load()
{
	local temp
	local MODEL SERIAL

	# Default value for MODEL and SERIAL
	MODEL="XXX"
	SERIAL="FFFFFFFFFFFF"

	log "# Starting Self diagnostics tests #"
	if [ "${JSON_OUT}" -eq 1 ]; then
		json_init
		json_add_string "report_dir" "${REPORT_PATH}"
		JSON_OUT_BUFFER="$(json_dump)"
	else
		echo "report_dir: ${REPORT_PATH}"
	fi

	temp="$(db -q get device.deviceinfo.ModelName)"
	[ -n "${temp}" ] && \
		MODEL="${temp}"

	temp="$(db -q get device.deviceinfo.SerialNumber)"
	[ -n "${temp}" ] && \
		SERIAL="${temp}"

	temp="$(uci -q get self-diagnostics.globals.extended_spec_dir)"
	[ -d "${temp}" ] && \
		SPEC_EXT_DIR="${temp}"

	temp="$(uci -q get self-diagnostics.globals.exec_timeout)"
	[ -n "${temp}" ] && \
		TIMEOUT="${temp}"

	temp="$(uci -q get self-diagnostics.globals.report_name)"
	[ -n "${temp}" ] && \
		REPORT_NAME="$(eval echo "${temp}")"

	REPORT_NAME="${REPORT_NAME//[ \/]/_}"

	temp="$(uci -q get self-diagnostics.globals.compression_level)"
	[ -n "${temp}" ] && \
		COMPOPTS="${temp}"

	temp="$(uci -q get self-diagnostics.globals.verbose)"
	[ -n "${temp}" ] && \
		VERBOSE="${temp}"
}

# shellcheck disable=SC2129,SC3043
run_cmd()
{
	local exec_timeout name cmd description
	local export_path rc start_time end_time

	exec_timeout="${1}"; shift
	name="${1}"; shift
	cmd="${1}"; shift
	description="${*}"

	start_time="$(date +%s)"
	export_path="${REPORT_TEMP_DIR}/${name}.log"
	log "Executing $cmd with timeout $exec_timeout"
	echo "##########################################" >> "$export_path"
	echo "# $description #">> "$export_path"
	echo "# Exec [$cmd], timeout [$exec_timeout], start_time [$(date +%Y:%m:%d-%H:%M:%S)] #" >> "$export_path"
	echo "##########################################" >> "$export_path"
	eval timeout "${exec_timeout}" "$cmd" >> "$export_path" 2>&1
	rc=$?
	end_time="$(date +%s)"
	echo "######## Execution done in [$((end_time - start_time)) ], return code $rc ######" >> "$export_path"

	if [ "$rc" -eq 0 ]; then
		log "Execution [$cmd] completed"
	else
		err_log "Execution [$cmd] Failed/Timeout with $rc exit code"
	fi

	echo >> "$export_path"
}

# shellcheck disable=SC2154,SC3060,SC3043
exec_spec()
{
	local json_file exec_skip name timeout exec_timeout start_time end_time

	start_time="$(date +%s)"
	json_file="$1"
	[ -z "$json_file" ] && {
		err_log "No/invalid spec json_file"
		return 1
	}

	log "# Loading $json_file at ${start_time} ..."

	json_init
	json_load_file "${json_file}" || {
		err_log "Failed to load ${json_file} spec file"
		return 1
	}

	name="$(basename "${json_file}")"
	exec_skip=0
	if json_is_a dependency array; then
		json_select "dependency"
		json_get_keys ekeys

		for key in $ekeys; do
			if json_is_a "$key" object; then
				json_select "$key"
				json_get_var type type

				if [ "$type" = "file" ]; then
					json_get_var file file
					if [ ! -e "$file" ]; then
						err_log "${json_file} has unmet file dependency $file"
						exec_skip=1
						json_select ..
						continue
					fi
				fi
				json_select ..
			fi
		done
		json_select ..
	fi

	[ "${exec_skip}" -eq 1 ] && {
		err_log "Dependency not satisfied for ${json_file}"
		return 0
	}

	json_get_var description description
	log "Description: $description"

	if json_is_a exec array; then
		json_select "exec"
		json_get_keys keys

		for key in $keys; do
			if json_is_a "$key" object; then
				json_select "${key}"
				local cmd_skip file

				cmd_skip=0
				file=""
				if json_is_a dependency array; then
					json_select "dependency"
					json_get_keys d_keys

					for d_key in ${d_keys}; do
						if json_is_a "${d_key}" object; then
							json_select "${d_key}"
							json_get_var type type
							if [ "$type" = "file" ]; then
								json_get_var file file
								if [ ! -e "${file}" ]; then
									json_select ..
									cmd_skip=1
									continue
								fi
							fi
							json_select ..
						fi
					done
					json_select ..
				fi

				[ $cmd_skip -eq 1 ] && {
					json_select ..
					err_log "Dependency not satisfied for ${file}"
					continue
				}

				json_get_var description description
				json_get_var cmd cmd
				json_get_var timeout timeout

				if [ -n "$timeout" ]; then
					exec_timeout=$timeout
				else
					exec_timeout=$TIMEOUT
				fi
				run_cmd "${exec_timeout}" "${name//.json/}" "${cmd}" "${description}"
				json_select ..
			fi
		done
		json_select ..
	fi

	end_time="$(date +%s)"
	log "# Handling of $json_file done, time: ${end_time}, time_spent: $((end_time - start_time)) sec"
	log ""
}

# shellcheck disable=SC3043,SC3060
generate_module()
{
	local modules="${*}"
	local file module

	config_load

	log "Modules [$*]"
	for module in $modules; do
		module="${module/.json/}"
		file="$(find "${SPEC_DIR}" -type f -name "${module}.json")"
		[ -z "$file" ] && {
			[ -d "${SPEC_EXT_DIR}" ] && \
				file="$(find "${SPEC_EXT_DIR}" -type f -name "${module}.json")"
		}

		[ -f "$file" ] && \
			exec_spec "$file"
	done
}

# shellcheck disable=SC3043
generate_all()
{
	local files

	config_load

	files="$(find "${SPEC_DIR}" -type f -name "*.json")"
	[ -d "${SPEC_EXT_DIR}" ] && \
		files="${files} $(find "${SPEC_EXT_DIR}" -type f -name "*.json")"

	[ -z "$files" ] && {
		return 0
	}

	for file in $files; do
		exec_spec "$file"
	done

}

# shellcheck disable=SC3060,SC3043
list_modules()
{
	local files

	if [ "${JSON_OUT}" -eq 1 ]; then
		json_init
		json_add_array "CoreModules"
	else
		echo
		echo "Core Module(s):"
	fi

	cd ${SPEC_DIR} && {
		for file in *.json; do
			if [ "${JSON_OUT}" -eq 1 ]; then
				json_add_string "" "${file/.json/}"
			else
				echo " - ${file/.json/}"
			fi
		done
	}

	if [ "${JSON_OUT}" -eq 1 ]; then
		json_close_array
		json_add_array "ExtensionModules"
	else
		echo
		echo "Extension Module(s):"
	fi

	cd ${SPEC_EXT_DIR} && {
		for file in *.json; do
			if [ "${JSON_OUT}" -eq 1 ]; then
				json_add_string "" "${file/.json/}"
			else
				echo " - ${file/.json/}"
			fi
		done
	}

	if [ "${JSON_OUT}" -eq 1 ]; then
		json_close_array
		json_dump
	else
		echo
	fi
}

[ ! -d "${SPEC_DIR}" ] && {
	log "# ${SPEC_DIR} does not exist"
	exit 1
}

list=0
modules=""

while getopts "m:hlj" opts; do
	case $opts in
		h)
			help
			exit;;
		j)
			JSON_OUT=1
			;;
		l)
			list=1
			;;
		m)
			modules="$modules ${OPTARG}"
			;;
		*)
			help
			exit
			;;
	esac
done

if [ "${list}" -eq 1 ]; then
	list_modules
	exit 0
fi

if [ -z "${modules}" ]; then
	generate_all
else
	generate_module "${modules}"
fi
