require "luci.sys"
local api = require "luci.model.cbi.passwall.api.api"

local var = api.get_args(arg)
local FLAG = var["-FLAG"]
local SMARTDNS_CONF = var["-SMARTDNS_CONF"]
local LOCAL_GROUP = var["-LOCAL_GROUP"]
local REMOTE_GROUP = var["-REMOTE_GROUP"]
local REMOTE_FAKEDNS = var["-REMOTE_FAKEDNS"]
local TUN_DNS = var["-TUN_DNS"]
local TCP_NODE = var["-TCP_NODE"]
local PROXY_MODE = var["-PROXY_MODE"]
local NO_PROXY_IPV6 = var["-NO_PROXY_IPV6"]
local NO_LOGIC_LOG = var["-NO_LOGIC_LOG"]
local LOG_FILE = api.LOG_FILE
local CACHE_PATH = api.CACHE_PATH
local CACHE_FLAG = "dns_" .. FLAG
local CACHE_DNS_PATH = CACHE_PATH .. "/" .. CACHE_FLAG
local CACHE_DNS_FILE = CACHE_DNS_PATH .. ".conf"
local CACHE_TEXT_FILE = CACHE_DNS_PATH .. ".txt"
local SMARTDNS_PATH = "/tmp/etc/smartdns"

local uci = api.uci
local sys = api.sys
local jsonc = api.jsonc
local appname = api.appname
local fs = api.fs
local datatypes = api.datatypes

local list1 = {}
local excluded_domain = {}
local excluded_domain_str = "!"

local function log(...)
    if NO_LOGIC_LOG == "1" then
        return
    end
	local f, err = io.open(LOG_FILE, "a")
    if f and err == nil then
        local str = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ")
        f:write(str .. "\n")
        f:close()
    end
end

--从url获取域名
local function get_domain_from_url(url)
    if url then
        if datatypes.hostname(url) then
            return url
        end
        local domain = url:match("//([^/]+)")
        if domain then
            return domain
        end
    end
    return ""
end

local function check_ipset(domain, ipset)
    if domain == "" or domain:find("#") then
        return false
    end
    if not ipset then
        return
    end
	for k,v in ipairs(list1[domain].ipsets) do
		if ipset == v then
			return true
		end
	end
    return false
end

local function set_domain_address(domain, address)
    if domain == "" or domain:find("#") then
        return
    end
    if not list1[domain] then
        list1[domain] = {
            ipsets = {}
        }
    end
    if not list1[domain].address then
        list1[domain].address = address
    end
end

local function set_domain_group(domain, group)
    if domain == "" or domain:find("#") then
        return
    end
    if not group then
        return
    end
    if not list1[domain] then
        list1[domain] = {
            ipsets = {}
        }
    end
    if not list1[domain].group then
        list1[domain].group = group
        if group == REMOTE_GROUP then
            list1[domain].speed_check_mode = "none"
        end
    end
end

local function set_domain_ipset(domain, ipset)
    if domain == "" or domain:find("#") then
        return
    end
    if not ipset then
        return
    end
    if not list1[domain] then
        list1[domain] = {
            ipsets = {}
        }
    end
    for line in string.gmatch(ipset, '[^' .. "," .. ']+') do
        if not check_ipset(domain, line) then
            table.insert(list1[domain].ipsets, line)
        end
    end
end

local function add_excluded_domain(domain)
    if domain == "" or domain:find("#") then
        return
    end
	table.insert(excluded_domain, domain)
    excluded_domain_str = excluded_domain_str .. "|" .. domain
end

local function check_excluded_domain(domain)
    if domain == "" or domain:find("#") then
        return false
    end
	for k,v in ipairs(excluded_domain) do
		if domain:find(v) then
			return true
		end
	end
    return false
end

local cache_text = ""
local new_rules = luci.sys.exec("echo -n $(find /usr/share/passwall/rules -type f | xargs md5sum)")
local new_text = SMARTDNS_CONF .. LOCAL_GROUP .. REMOTE_GROUP .. REMOTE_FAKEDNS .. TUN_DNS .. PROXY_MODE .. NO_PROXY_IPV6 ..new_rules
if fs.access(CACHE_TEXT_FILE) then
    for line in io.lines(CACHE_TEXT_FILE) do
        cache_text = line
    end
end

if cache_text ~= new_text then
    api.remove(CACHE_DNS_PATH .. "*")
end

local global = PROXY_MODE:find("global")
local returnhome = PROXY_MODE:find("returnhome")
local chnlist = PROXY_MODE:find("chnroute")
local gfwlist = PROXY_MODE:find("gfwlist")

if not REMOTE_GROUP or REMOTE_GROUP == "nil" then
    REMOTE_GROUP = "passwall_proxy"
    if TUN_DNS then
        TUN_DNS = TUN_DNS:gsub("#", ":")
    end
    sys.call('sed -i "/passwall/d" /etc/smartdns/custom.conf >/dev/null 2>&1')
end

if not fs.access(CACHE_DNS_FILE) then
    sys.call(string.format('echo "server %s  -group %s -exclude-default-group" >> %s', TUN_DNS, REMOTE_GROUP, CACHE_DNS_FILE))
    --屏蔽列表
    for line in io.lines("/usr/share/passwall/rules/block_host") do
        if line ~= "" and not line:find("#") then
            set_domain_address(line, "-")
        end
    end

    --始终用国内DNS解析节点域名
    uci:foreach(appname, "nodes", function(t)
        local address = t.address
        if datatypes.hostname(address) then
            set_domain_group(address, LOCAL_GROUP)
            set_domain_ipset(address, "#4:vpsiplist,#6:vpsiplist6")
        end
    end)
    log(string.format("  - 节点列表中的域名(vpsiplist)使用分组：%s", LOCAL_GROUP or "默认"))

    --始终用国内DNS解析直连（白名单）列表
    for line in io.lines("/usr/share/passwall/rules/direct_host") do
        if line ~= "" and not line:find("#") then
            add_excluded_domain(line)
            set_domain_group(line, LOCAL_GROUP)
            set_domain_ipset(line, "#4:whitelist,#6:whitelist6")
        end
    end
    log(string.format("  - 域名白名单(whitelist)使用分组：%s", LOCAL_GROUP or "默认"))

    local fwd_group = LOCAL_GROUP
    local ipset_flag = "#4:whitelist,#6:whitelist6"
    local no_ipv6
    if uci:get(appname, "@global_subscribe[0]", "subscribe_proxy") or "0" == "1" then
        fwd_group = REMOTE_GROUP
        ipset_flag = "#4:blacklist,#6:blacklist6"
        if NO_PROXY_IPV6 == "1" then
            ipset_flag = "#4:blacklist"
            no_ipv6 = true
        end
        if REMOTE_FAKEDNS == "1" then
            ipset_flag = nil
        end
    end
    uci:foreach(appname, "subscribe_list", function(t)
        local domain = get_domain_from_url(t.url)
        if domain then
            if no_ipv6 then
                set_domain_address(domain, "#6")
            end
            set_domain_group(domain, fwd_group)
            set_domain_ipset(domain, ipset_flag)
        end
    end)
    log(string.format("  - 节点订阅域名(blacklist)使用分组：%s", fwd_group or "默认"))

    --始终使用远程DNS解析代理（黑名单）列表
    for line in io.lines("/usr/share/passwall/rules/proxy_host") do
        if line ~= "" and not line:find("#") then
            add_excluded_domain(line)
            local ipset_flag = "#4:blacklist,#6:blacklist6"
            if NO_PROXY_IPV6 == "1" then
                set_domain_address(line, "#6")
                ipset_flag = "#4:blacklist"
            end
            if REMOTE_FAKEDNS == "1" then
                ipset_flag = nil
            end
            set_domain_group(line, REMOTE_GROUP)
            set_domain_ipset(line, ipset_flag)
        end
    end
    log(string.format("  - 代理域名表(blacklist)使用分组：%s", REMOTE_GROUP or "默认"))

    --分流规则
    if uci:get(appname, TCP_NODE, "protocol") == "_shunt" then
        local t = uci:get_all(appname, TCP_NODE)
        local default_node_id = t["default_node"] or "_direct"
        uci:foreach(appname, "shunt_rules", function(s)
            local _node_id = t[s[".name"]] or "nil"
            if _node_id ~= "nil" and _node_id ~= "_blackhole" then
                if _node_id == "_default" then
                    _node_id = default_node_id
                end

                fwd_group = nil
                ipset_flag = nil
                no_ipv6 = nil

                if _node_id == "_direct" then
                    fwd_group = LOCAL_GROUP
                    ipset_flag = "#4:whitelist,#6:whitelist6"
                else
                    fwd_group = REMOTE_GROUP
                    ipset_flag = "#4:shuntlist,#6:shuntlist6"
                    if NO_PROXY_IPV6 == "1" then
                        ipset_flag = "shuntlist"
                        no_ipv6 = true
                    end
                    if REMOTE_FAKEDNS == "1" then
                        ipset_flag = nil
                    end
                end

                local domain_list = s.domain_list or ""
                for line in string.gmatch(domain_list, "[^\r\n]+") do
                    if line ~= "" and not line:find("#") and not line:find("regexp:") and not line:find("geosite:") and not line:find("ext:") then
                        if line:find("domain:") or line:find("full:") then
                            line = string.match(line, ":([^:]+)$")
                        end
                        add_excluded_domain(line)
                        
                        if no_ipv6 then
                            set_domain_address(line, "#6")
                        end
                        set_domain_group(line, fwd_group)
                        set_domain_ipset(line, ipset_flag)
                    end
                end
                if _node_id ~= "_direct" then
                    log(string.format("  - V2ray/Xray分流规则(%s)使用分组：%s", s.remarks, fwd_group or "默认"))
                end
            end
        end)
    end

    --如果没有使用回国模式
    if not returnhome then
        if fs.access("/usr/share/passwall/rules/gfwlist") then
            local gfwlist_str = sys.exec('cat /usr/share/passwall/rules/gfwlist | grep -v -E "^#" | grep -v -E "' .. excluded_domain_str .. '"')
            for line in string.gmatch(gfwlist_str, "[^\r\n]+") do
                if line ~= "" then
                    local ipset_flag = "#4:gfwlist,#6:gfwlist6"
                    if NO_PROXY_IPV6 == "1" then
                        ipset_flag = "#4:gfwlist"
                        set_domain_address(line, "#6")
                    end
                    fwd_group = REMOTE_GROUP
                    if REMOTE_FAKEDNS == "1" then
                        ipset_flag = nil
                    end
                    set_domain_group(line, fwd_group)
                    set_domain_ipset(line, ipset_flag)
                end
            end
            log(string.format("  - 防火墙域名表(gfwlist)使用分组：%s", fwd_group or "默认"))
        end

        if fs.access("/usr/share/passwall/rules/chnlist") and chnlist then
            local chnlist_str = sys.exec('cat /usr/share/passwall/rules/chnlist | grep -v -E "^#" | grep -v -E "' .. excluded_domain_str .. '"')
            for line in string.gmatch(chnlist_str, "[^\r\n]+") do
                if line ~= "" then
                    set_domain_group(line, LOCAL_GROUP)
                    set_domain_ipset(line, "#4:chnroute,#6:chnroute6")
                end
            end
        end
        log(string.format("  - 中国域名表(chnroute)使用分组：%s", LOCAL_GROUP or "默认"))
    else
        if fs.access("/usr/share/passwall/rules/chnlist") then
            local chnlist_str = sys.exec('cat /usr/share/passwall/rules/chnlist | grep -v -E "^#" | grep -v -E "' .. excluded_domain_str .. '"')
            for line in string.gmatch(chnlist_str, "[^\r\n]+") do
                if line ~= "" then
                    local ipset_flag = "#4:chnroute,#6:chnroute6"
                    if NO_PROXY_IPV6 == "1" then
                        ipset_flag = "#4:chnroute"
                        set_domain_address(line, "#6")
                    end
                    set_domain_group(line, REMOTE_GROUP)
                    if REMOTE_FAKEDNS == "1" then
                        ipset_flag = nil
                    end
                    set_domain_ipset(line, ipset_flag)
                end
            end
            log(string.format("  - 中国域名表(chnroute)使用分组：%s", REMOTE_GROUP or "默认"))
        end
    end

    local f_out = io.open(CACHE_DNS_FILE, "a")
    for key, value in pairs(list1) do
        local group_str = ""
        local ipset_str = ""
        local speed_check_mode_str = ""
        local address_str = ""
        if value.group and #value.group > 0 then
            group_str = group_str .. value.group
        end
        if group_str ~= "" then
            group_str = " -n " .. group_str
        end
        if value.ipsets and #value.ipsets > 0 then
            for i, ipset in ipairs(value.ipsets) do
                ipset_str = ipset_str .. ipset .. ","
            end
            ipset_str = ipset_str:sub(1, #ipset_str - 1)
        end
        if ipset_str ~= "" then
            ipset_str = " -p " .. ipset_str
        end
        if value.address and #value.address > 0 then
            address_str = address_str .. value.address
        end
        if address_str ~= "" then
            address_str = " -a " .. address_str
        end
        if value.speed_check_mode and #value.speed_check_mode > 0 then
            speed_check_mode_str = value.speed_check_mode
        end
        if speed_check_mode_str ~= "" then
            speed_check_mode_str = " -c " .. speed_check_mode_str
        end
        local str = string.format("domain-rules /%s/ %s%s%s%s\n", key, group_str, ipset_str, address_str, speed_check_mode_str)
        f_out:write(str)
    end
    f_out:close()

    f_out = io.open(CACHE_TEXT_FILE, "a")
    f_out:write(new_text)
    f_out:close()
end
fs.symlink(CACHE_DNS_FILE, SMARTDNS_CONF)
sys.call(string.format('echo "conf-file %s" >> /etc/smartdns/custom.conf', SMARTDNS_CONF))
log("  - 请让SmartDNS作为Dnsmasq的上游或重定向！")
