#!/usr/bin/env bash
# =============================================================================
# onx-firewall-list — Enumerate active firewall rules across all backends
#
# Input (stdin JSON):
#   { "zone": "public" }   -- optional, defaults to "public" for firewalld
#
# Output (stdout JSON):
#   {
#     "backend":   "firewalld|ufw|nftables",
#     "zones":     ["public","trusted",...],
#     "rules": [
#       { "type":"ip", "value":"1.2.3.4", "action":"reject", "raw":"rule ..." },
#       { "type":"port", "value":"443/tcp", "action":"accept", "raw":"..." }
#     ]
#   }
#
# Deployed to: /usr/local/onoxsoft/bin/onx-firewall-list
# =============================================================================

set -euo pipefail

SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
# shellcheck source=_lib/common.sh
source "${SCRIPT_DIR}/_lib/common.sh"

require_root
onx_json_input

ZONE=$(onx_json_field "zone" "public")
[[ "${ZONE}" =~ ^[a-zA-Z0-9_-]{1,32}$ ]] || onx_die 1 "invalid zone '${ZONE}'"

BACKEND=""
if command -v firewall-cmd >/dev/null 2>&1; then
    BACKEND="firewalld"
elif command -v ufw >/dev/null 2>&1; then
    BACKEND="ufw"
elif command -v nft >/dev/null 2>&1; then
    BACKEND="nftables"
else
    onx_die 2 "no firewall backend found"
fi

if [[ "${MOCK_MODE}" == "1" ]]; then
    jq -nc --arg b "${BACKEND}" --arg z "${ZONE}" \
        '{backend:$b, zones:[$z,"trusted"], rules:[
          {type:"ip", value:"1.2.3.4", action:"reject", raw:"mock"},
          {type:"port", value:"443/tcp", action:"accept", raw:"mock"}
        ]}'
    exit 0
fi

RULES_JSON="[]"
ZONES_JSON="[\"${ZONE}\"]"

if [[ "${BACKEND}" == "firewalld" ]]; then
    ZONES_RAW=$(firewall-cmd --get-zones 2>/dev/null || echo "${ZONE}")
    ZONES_JSON=$(printf '%s' "${ZONES_RAW}" | tr ' ' '\n' | jq -R -s -c 'split("\n") | map(select(length>0))')

    RICH_LINES=$(firewall-cmd --zone="${ZONE}" --list-rich-rules 2>/dev/null || echo "")
    SERVICES=$(firewall-cmd  --zone="${ZONE}" --list-services    2>/dev/null || echo "")
    PORTS=$(firewall-cmd     --zone="${ZONE}" --list-ports       2>/dev/null || echo "")
    SOURCES=$(firewall-cmd   --zone="${ZONE}" --list-sources     2>/dev/null || echo "")

    RULES_JSON=$(jq -nc \
        --arg rich   "${RICH_LINES}" \
        --arg svcs   "${SERVICES}"    \
        --arg ports  "${PORTS}"       \
        --arg srcs   "${SOURCES}"     \
        '
        def parse_rich: split("\n") | map(select(length>0) | {
            raw: .,
            type: (
                if test("source address=") then "ip"
                elif test("port port=")    then "port"
                elif test("service name=") then "service"
                else "rich" end
            ),
            value: (
                if test("source address=\"") then (capture("source address=\"(?<v>[^\"]+)\"").v)
                elif test("port port=\"")    then (capture("port port=\"(?<v>[^\"]+)\"").v)
                elif test("service name=\"") then (capture("service name=\"(?<v>[^\"]+)\"").v)
                else . end
            ),
            action: (
                if test(" accept") then "accept"
                elif test(" reject") then "reject"
                elif test(" drop") then "drop"
                else "" end
            )
        });

        [
          ($rich  | parse_rich),
          ($svcs  | split(" ") | map(select(length>0) | {raw:., type:"service", value:., action:"accept"})),
          ($ports | split(" ") | map(select(length>0) | {raw:., type:"port",    value:., action:"accept"})),
          ($srcs  | split(" ") | map(select(length>0) | {raw:., type:"ip",      value:., action:"accept"}))
        ] | add
        ')

elif [[ "${BACKEND}" == "ufw" ]]; then
    STATUS=$(ufw status numbered 2>/dev/null || echo "")
    RULES_JSON=$(printf '%s' "${STATUS}" | jq -R -s -c '
        split("\n")
        | map(select(test("^\\[\\s*[0-9]+\\]")))
        | map({
            raw: .,
            type: ( if test("/(tcp|udp)") then "port" else "ip" end ),
            value: (capture("\\]\\s+(?<v>\\S+)").v // ""),
            action: ( if test("ALLOW") then "accept" elif test("REJECT") then "reject" else "drop" end )
        })
    ')

elif [[ "${BACKEND}" == "nftables" ]]; then
    RAW=$(nft -j list ruleset 2>/dev/null || echo '{"nftables":[]}')
    RULES_JSON=$(printf '%s' "${RAW}" | jq -c '
        .nftables
        | map(select(.rule? != null))
        | map(.rule)
        | map({
            raw: (.expr // [] | tostring),
            type: "rich",
            value: "",
            action: ((.expr // []) | map(select(.. | objects | .verdict?)) | first.verdict?.kind? // "")
          })
    ')
fi

jq -nc \
    --arg b "${BACKEND}" \
    --argjson zones "${ZONES_JSON}" \
    --argjson rules "${RULES_JSON}" \
    '{backend:$b, zones:$zones, rules:$rules}'
