#!/usr/bin/lua

-- Created by jiaqiang.ye@lnxall.com
-- 2024/02/20
-- for Non-openwrt systems

local uci = require "uci"
local cjson = require "cjson"
local invoker = require "invoker"

local gfmt = string.format

local function get_netdev(name)
	local nuci = uci.cursor('/etc/config')
	if not nuci then
		io.stderr:write("Error, failed to local uci-conf from /etc/config\n")
		io.stderr:flush()
		return nil
	end

	if name == 'lan' then
		local ports = nuci:get("network", '@device[0]', 'ports')
		nuci:close(); nuci = nil
		if type(ports) ~= "table" or type(ports[1]) ~= "string" then
			io.stderr:write("Error, failed to find ports for LAN.\n")
			io.stderr:flush()
			return false
		end
		return table.concat(ports, " ")
	end

	local ndev = nuci:get('network', name, 'device')
	nuci:close(); nuci = nil
	if type(ndev) ~= "string" or #ndev == 0 then
		io.stderr:write(gfmt("Error, failed to determine network device for '%s'\n", name))
		io.stderr:flush()
		return nil
	end

	return ndev
end

local function get_proto(name)
	local nuci, proto = uci.cursor('/etc/config'), nil
	if nuci then
		proto = nuci:get('network', name, 'proto')
		nuci:close(); nuci = nil
	end

	if not proto then
		io.stderr:write(gfmt("Error, failed to get proto for '%s'\n", name))
		io.stderr:flush()
		return ""
	end
	return proto
end

local function get_macaddr(ndev)
	local okay, output = invoker.invoke(invoker.OUTPUT + invoker.NOSTDIO,
		"ip", "addr", "show", ndev)
	if okay ~= 0 or type(output) ~= "string" then
		io.stderr:write(gfmt("Error, network device not found: %s\n", ndev))
		io.stderr:flush()
		return "", ""
	end

	local macaddr = string.match(output, "link/ether%s+([%x:]+)%s+brd")
	if not macaddr then
		io.stderr:write(gfmt("Error, no MAC address found for %s => %s\n", ndev, output))
		io.stderr:flush()
		return "", ""
	end

	local updown = string.find(output, "state UP", 1, true) and "1" or "0"
	local ipaddr = string.match(output, "inet%s+([%d./]+)%s+brd")
	return macaddr, updown, ipaddr
end

local function fill_status(name, ninfo)
	local netdev = name == "lan" and "br-lan" or ninfo.l3_device
	if type(netdev) ~= "string" or #netdev == 0 then netdev = "non0" end

	ninfo['uptime'] = invoker.uptime() -- what does this mean?
	ninfo['macaddr'], ninfo['status'], ninfo['subnet'] = get_macaddr(netdev)

	ninfo["dnss"] = ""
	-- get DNS via `nmcli con show name
	local okay, output = invoker.invoke(0xF0000 + invoker.OUTPUT + invoker.NOSTDIO,
		"nmcli", "con", "show", name)
	if okay == 0 and type(output) == "string" then
		local dnss = string.match(output, "domain_name_servers = ([^\r\n]+)")
		if dnss then ninfo.dnss = dnss end
	end

	local prefix = gfmt("/sys/class/net/%s/", netdev)
	ninfo['speed'] = invoker.readfile(prefix .. 'speed', invoker.TRIMEND) or ""

	prefix = prefix .. 'statistics/'
	ninfo['txbytes'] = invoker.readfile(prefix .. 'tx_bytes', invoker.TRIMEND) or ""
	ninfo['rxbytes'] = invoker.readfile(prefix .. 'rx_bytes', invoker.TRIMEND) or ""
	ninfo['txpackets'] = invoker.readfile(prefix .. 'tx_packets', invoker.TRIMEND) or ""
	ninfo['rxpackets'] = invoker.readfile(prefix .. 'rx_packets', invoker.TRIMEND) or ""
	return true
end

local function fetch_main()
	local allinfo = {}

	local waninfo, wan1_info, winfo, linfo = {}, {}, {}, {}
	waninfo.l3_device = get_netdev('wan')
	waninfo.proto = get_proto('wan')
	fill_status('wan', waninfo)
	allinfo['wan'] = waninfo

	local ldev1, proto1 = get_netdev('wan1'), get_proto('wan1')
	if ldev1 and proto1 then
		wan1_info.l3_device = ldev1
		wan1_info.proto = proto1
		fill_status('wan1', wan1_info)
		allinfo['wan1'] = wan1_info
	end

	winfo.l3_device = get_netdev('wwan')
	winfo.proto = get_proto('wwan')
	fill_status('wwan', winfo)
	allinfo['wwan'] = winfo

	linfo.l3_device = get_netdev('lan')
	linfo.proto = get_proto('lan')
	fill_status('lan', linfo)
	allinfo['lan'] = linfo

	io.stdout:write(cjson.encode(allinfo))
	io.stdout:flush()
	return true
end

fetch_main()
os.exit(0)
