#!/bin/sh
# shellcheck disable=SC2034  # codacy:Unused variables

dnsmasq_configure() {
    local CONFIG=$1

    load_ui_response
    load_xrayui_config

    log_info "Configuring dnsmasq..."

    # Check if 'xray' process is running
    local xray_pid=$(get_proc "xray")
    if [ -z "$xray_pid" ]; then
        log_warn "Xray process not found. Skipping client firewall configuration."
        return
    fi

    log_debug "config: $CONFIG"
    log_debug "XRAY_CONFIG_FILE: $XRAY_CONFIG_FILE"
    log_debug "logs_dnsmasq setting: $logs_dnsmasq"

    touch "$CONFIG"
    sed -i "/^#${ADDON_TAG} start$/,/^#${ADDON_TAG} end$/d" "$CONFIG"

    pc_append "" "$CONFIG"
    pc_append "#$ADDON_TAG start" "$CONFIG"

    if [ "$logs_dnsmasq" = "true" ]; then

        grep -qE '^log-queries' "$CONFIG" || pc_append "log-queries" "$CONFIG" >/dev/null && log_debug "log-queries enabled"
        grep -qE '^log-async' "$CONFIG" || pc_append "log-async=25" "$CONFIG" >/dev/null && log_debug "log-async enabled"
        grep -qE '^log-facility' "$CONFIG" || pc_append "log-facility=/opt/var/log/dnsmasq.log" "$CONFIG" >/dev/null && log_debug "log-facility enabled"
    fi

    log_debug "dnsmasq: found inbound DNS server"

    local has_dnsmasq_servers
    jq -r '
        .inbounds[]
        | select(.protocol == "dokodemo-door")
        | select(.listen // "127.0.0.1")
        | select(.settings and (.settings | length > 0))
        | select(.settings.followRedirect != true)
        | select((.settings.port // 0) == 53)
        | "\(.listen // "127.0.0.1")#\(.port)"
 ' "$XRAY_CONFIG_FILE" >/tmp/xrayui-dnsmasq-servers.$$

    while IFS= read -r srv; do
        pc_append "no-resolv" "$CONFIG" && log_debug "dnsmasq: no-resolv enabled"
        pc_append "server=$srv" "$CONFIG" && log_debug "dnsmasq: added inbound DNS server=$srv"
        has_dnsmasq_servers="true"
    done </tmp/xrayui-dnsmasq-servers.$$

    rm -f /tmp/xrayui-dnsmasq-servers.$$

    if [ "$xray_dns_only" = "true" ] && [ "$has_dnsmasq_servers" = "true" ]; then
        log_debug "dnsmasq: xray_dns_only is enabled, disabling all other DNS"
        sed -i '/^[[:space:]]*servers-file=/ s/^/#/' "$CONFIG" && log_debug "dnsmasq: commented servers-file"
    fi

    dnsmasq_xray_ipset_domains $CONFIG

    pc_append "#$ADDON_TAG end" "$CONFIG"

    log_ok "dnsmasq configured"
}

dnsmasq_xray_ipset_domains() {
    CONFIG="$1"

    local DNSMASQ_DIR="$ADDON_SHARE_DIR/dnsmasq"
    local DIRECT_CONF="$DNSMASQ_DIR/direct.conf"
    local IPSET_CONF="$DNSMASQ_DIR/ipset.rules"
    local TMP="/tmp/xrayui_dnsmasq_direct.tmp.$$"

    local V2DAT="$ADDON_SHARE_DIR/v2dat"
    local GEOSITE="/opt/sbin/geosite.dat"
    local GEOSITE_XRAUI="/opt/sbin/xrayui"
    local GEOIP="/opt/sbin/geoip.dat"

    local ipset_mode="${ipsec:-off}"

    log_debug "dnsmasq ipset mode: $ipset_mode"

    mkdir -p "$DNSMASQ_DIR" 2>/dev/null || return 1

    rm -f "$DIRECT_CONF" "$IPSET_CONF" "$TMP" 2>/dev/null

    exec 3>"$TMP"
    printf '# Autogenerated by %s on %s\n' \
        "$ADDON_TAG" "$(date '+%Y-%m-%d %H:%M:%S')" >&3

    case "$ipset_mode" in
    bypass)
        SET_V4="$IPSET_BYPASS_V4"
        SET_V6="$IPSET_BYPASS_V6"
        JQ_FILTER='
              [ .outbounds[] | select(.protocol=="freedom") | .tag ] as $free
              | .routing.rules[]
              | select((.outboundTag as $t | $free | index($t)))'
        ;;
    redirect)
        SET_V4="$IPSET_PROXY_V4"
        SET_V6="$IPSET_PROXY_V6"
        JQ_FILTER='
              [ .outbounds[] | select(.protocol=="freedom") | .tag ] as $free
              | .routing.rules[]
              | select(((.outboundTag as $t | $free | index($t)) | not))'
        ;;
    off | *)
        JQ_FILTER= # nothing
        ;;
    esac

    if [ -n "$JQ_FILTER" ]; then

        tags_list=""
        tags_xrayui_list=""
        ip_list=""

        while IFS= read -r entry; do
            case "$entry" in
            regexp:* | regex:*) ;;
            geosite:*) tags_list="$tags_list ${entry#geosite:}" ;;
            geoip:*) ip_list="$ip_list ${entry#geoip:}" ;;
            ext:xrayui:*) tags_xrayui_list="$tags_xrayui_list ${entry#ext:xrayui:}" ;;
            domain:*) dnsmasq_domain_to_ipset "${entry#domain:}" "$SET_V4" "$SET_V6" ;;
            .*) dnsmasq_domain_to_ipset "${entry#.}" "$SET_V4" "$SET_V6" ;;
            [0-9]* | *:*) ip_list="$ip_list ${entry}" ;;
            *.*) dnsmasq_domain_to_ipset "$entry" "$SET_V4" "$SET_V6" ;;
            esac
        done <<EOF
$(jq -r "$JQ_FILTER
      | ((.domain // []) + (.ip // []))
      | (if type==\"array\" then .[] else . end)" "$XRAY_CONFIG_FILE" | sort -u)
EOF

        if [ -n "$tags_list" ]; then
            log_debug "dnsmasq: unpacking geosite: $tags_list"
            set -- # clear "$@"
            for t in $(printf '%s\n' $tags_list | sort -u); do
                set -- "$@" -f "$t"
            done
            $IONICE $NICE "$V2DAT" unpack geosite -p "$@" "$GEOSITE" |
                awk '!/^#/ && !/^(keyword:|regexp:|full:)/' |
                dnsmasq_domain_to_ipset_bulk "$SET_V4" "$SET_V6" "$(is_ipv6_enabled)" >&3
        fi

        if [ -n "$tags_xrayui_list" ]; then
            log_debug "dnsmasq: unpacking xrayui geosite: $tags_xrayui_list"
            set -- # clear "$@"
            for t in $(printf '%s\n' $tags_xrayui_list | sort -u); do
                set -- "$@" -f "$t"
            done
            $IONICE $NICE "$V2DAT" unpack geosite -p "$@" "$GEOSITE_XRAUI" |
                awk '!/^#/ && !/^(keyword:|regexp:|full:)/' |
                dnsmasq_domain_to_ipset_bulk "$SET_V4" "$SET_V6" "$(is_ipv6_enabled)" >&3
        fi

        if [ -n "$ip_list" ]; then
            log_debug "dnsmasq: unpacking geoip: $ip_list"
            set --
            for t in $(printf '%s\n' $ip_list | sort -u); do
                set -- "$@" -f "$t"
            done

            G4="/tmp/xrayui_geoip4.$$"
            G6="/tmp/xrayui_geoip6.$$"
            : >"$G4"
            : >"$G6"

            $IONICE $NICE "$V2DAT" unpack geoip -p "$@" "$GEOIP" | awk '
        /^[#]/ {next}
        index($0,":") {print > "'"$G6"'" ; next}
        {print > "'"$G4"'"}'

            IPSET_CONF_NEW="$IPSET_CONF.$$"
            : >"$IPSET_CONF_NEW"

            dnsmasq_nets_to_ipset_bulk "$SET_V4" inet "$G4" "$IPSET_CONF_NEW"
            if is_ipv6_enabled; then
                dnsmasq_nets_to_ipset_bulk "$SET_V6" inet6 "$G6" "$IPSET_CONF_NEW"
            fi

            ipset restore <"$IPSET_CONF_NEW"
            for t in $(printf '%s\n' $ip_list | sort -u); do
                if printf %s "$t" | grep -Eq '^([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]{1,2})?$'; then
                    ipset add "$SET_V4" "$t" timeout 0 2>/dev/null
                elif printf %s "$t" | grep -Eq '^[0-9A-Fa-f:]+(/[0-9]{1,3})?$'; then
                    ipset add "$SET_V6" "$t" timeout 0 2>/dev/null
                else
                    set -- "$@" -f "$t"
                fi
            done

            mv -f "$IPSET_CONF_NEW" "$IPSET_CONF"
            rm -f "$G4" "$G6"
        fi
    fi

    exec 3>&- # close FD 3

    {
        head -n1 "$TMP"
        tail -n +2 "$TMP" | sort -u
    } >"${TMP}.uniq" &&
        mv -f "${TMP}.uniq" "$TMP"

    chmod 644 "$TMP"
    mv -f "$TMP" "$DIRECT_CONF"

    grep -qF "conf-file=$DIRECT_CONF" "$CONFIG" ||
        pc_append "conf-file=$DIRECT_CONF" "$CONFIG"
}

dnsmasq_domain_to_ipset() {
    local d="$1"

    local set_v4="$2"
    local set_v6="$3"

    if is_ipv6_enabled; then
        printf 'ipset=/%s/%s,%s\n' "$d" "$set_v4" "$set_v6" >&3
    else
        printf 'ipset=/%s/%s\n' "$d" "$set_v4" >&3
    fi
}

dnsmasq_domain_to_ipset_bulk() {
    local set_v4="$1"
    local set_v6="$2"
    local ipv6_enabled="$3"
    awk -v v4="$set_v4" -v v6="$set_v6" -v ip6="$ipv6_enabled" '
        NF {
            if (ip6) printf "ipset=/%s/%s,%s\n",$0,v4,v6
            else     printf "ipset=/%s/%s\n",$0,v4
        }'
}

dnsmasq_nets_to_ipset_bulk() {
    s="$1"
    fam="$2"
    src="$3"
    rules="$4"
    n=$(wc -l <"$src" 2>/dev/null || echo 0)
    hs=1024
    [ "$n" -gt 2000 ] && hs=4096
    [ "$n" -gt 8000 ] && hs=16384
    [ "$n" -gt 32000 ] && hs=65536
    [ "$n" -gt 128000 ] && hs=262144
    mx=$((n + n / 2 + 4096))
    [ "$mx" -lt 65536 ] && mx=65536
    printf 'create -exist %s hash:net family %s hashsize %s maxelem %s timeout 86400\n' "$s" "$fam" "$hs" "$mx" >>"$rules"
    t="${s}_tmp_$$"
    printf 'create -exist %s hash:net family %s hashsize %s maxelem %s timeout 86400\n' "$t" "$fam" "$hs" "$mx" >>"$rules"
    if [ -s "$src" ]; then LC_ALL=C sort -u "$src" | awk -v S="$t" 'NF{printf "add %s %s timeout 0\n",S,$0}' >>"$rules"; fi
    printf 'swap %s %s\n' "$s" "$t" >>"$rules"
    printf 'destroy %s\n' "$t" >>"$rules"
}

dnsmasq_restart() {
    log_info "Restarting dnsmasq"
    update_loading_progress "Restarting dnsmasq..."

    { service restart_dnsmasq >/dev/null 2>&1 && log_ok "DNS service restarted successfully."; } || log_error "Failed to restart DNS service."
}
