function fw::block_ip() {
  local client_ip="${1:-}" target_ip="${2:-}"

  fw::_forward_exists -s "$client_ip" -d "$target_ip" -j DROP \
  || iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -j DROP

  fw::_forward_exists -s "$client_ip" -d "$target_ip" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
  || iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:"
}

function fw::unblock_ip() {
  local client_ip="${1:-}" target_ip="${2:-}"
  
  fw::_forward_exists -s "$client_ip" -d "$target_ip" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
  && iptables -D FORWARD -s "$client_ip" -d "$target_ip" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" 2>/dev/null || true

  fw::_forward_exists -s "$client_ip" -d "$target_ip" -j DROP \
  && iptables -D FORWARD -s "$client_ip" -d "$target_ip" -j DROP 2>/dev/null || true
}

function fw::block_port() {
  local client_ip="${1:-}" target_ip="${2:-}" port="${3:-}" proto="${4:-tcp}"

  fw::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j DROP \
  || iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j DROP

  fw::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
  || iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:"

}

function fw::unblock_port() {
  local client_ip="${1:-}" target_ip="${2:-}" port="${3:-}" proto="${4:-tcp}"

  fw::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
  && iptables -D FORWARD -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" 2>/dev/null || true

  fw::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j DROP \
  && iptables -D FORWARD -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j DROP 2>/dev/null || true
}

function fw::block_all() {
  local client_ip="${1:-}" client_name="${2:-}"

  fw::_forward_exists -s "$client_ip" -j DROP \
  || iptables -A FORWARD -s "$client_ip" -j DROP

  log::debug "Blocked all traffic from: ${client_ip}"
}

function fw::unblock_all() {
  local client_ip="${1:-}"

  fw::_forward_exists -s "$client_ip" -j DROP \
  && iptables -D FORWARD -s "$client_ip" -j DROP 2>/dev/null || true

  monitor::unwatch "$client_ip"
  log::debug "Unblocked all traffic from: ${client_ip}"
}

function fw::block_subnet() {
  local client_ip="${1:-}" target_subnet="${2:-}"

  fw::_forward_exists -s "$client_ip" -d "$target_subnet" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
  || iptables -A FORWARD -s "$client_ip" -d "$target_subnet" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:"

  fw::_forward_exists -s "$client_ip" -d "$target_subnet" -j DROP \
  || iptables -A FORWARD -s "$client_ip" -d "$target_subnet" -j DROP
    
  log::wg_block "Blocked ${client_ip} → subnet ${target_subnet}"
}

function fw::unblock_subnet() {
  local client_ip="${1:-}" target_subnet="${2:-}"

  fw::_forward_exists -s "$client_ip" -d "$target_subnet" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
  && iptables -D FORWARD -s "$client_ip" -d "$target_subnet" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" 2>/dev/null || true

  fw::_forward_exists -s "$client_ip" -d "$target_subnet" -j DROP \
  && iptables -D FORWARD -s "$client_ip" -d "$target_subnet" -j DROP 2>/dev/null || true

  log::wg_unblock "Unblocked ${client_ip} → subnet ${target_subnet}"
}

function fw::allow_subnet() {
  local client_ip="${1:-}" target_subnet="${2:-}"
  
  fw::_forward_exists -s "$client_ip" -d "$target_subnet" -j ACCEPT \
  || iptables -I FORWARD 1 -s "$client_ip" -d "$target_subnet" -j ACCEPT
}

function fw::unallow_subnet() {
  local client_ip="${1:-}" target_subnet="${2:-}"

  fw::_forward_exists -s "$client_ip" -d "$target_subnet" -j ACCEPT \
  && iptables -D FORWARD -s "$client_ip" -d "$target_subnet" -j ACCEPT 2>/dev/null || true
}

function fw::allow_ip() {
  local client_ip="${1:-}" target_ip="${2:-}"

  fw::_forward_exists -s "$client_ip" -d "$target_ip" -j ACCEPT \
  || iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -j ACCEPT
}

function fw::unallow_ip() {
  local client_ip="${1:-}" target_ip="${2:-}"

  fw::_forward_exists -s "$client_ip" -d "$target_ip" -j ACCEPT \
  && iptables -D FORWARD -s "$client_ip" -d "$target_ip" -j ACCEPT 2>/dev/null || true
}

function fw::allow_port() {
  local client_ip="${1:-}" target_ip="${2:-}" port="${3:-}" proto="${4:-tcp}"

  fw::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j ACCEPT \
  || iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j ACCEPT
}

function fw::unallow_port() {
  local client_ip="${1:-}" target_ip="${2:-}" port="${3:-}" proto="${4:-tcp}"

  fw::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j ACCEPT \
  && iptables -D FORWARD -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j ACCEPT 2>/dev/null || true
}

function fw::flush_peer() {
  local client_ip="${1:?client_ip required}"
  log::debug "flush_peer: starting for $client_ip"

  # Collect line numbers into array
  local linenums=()
  while IFS= read -r linenum; do
    [[ -n "$linenum" ]] && linenums+=("$linenum")
  done < <(iptables -L FORWARD -n --line-numbers | grep -F "$client_ip" | awk '{print $1}')

  # Delete in reverse order (highest number first)
  local count=0
  local i
  for (( i=${#linenums[@]}-1; i>=0; i-- )); do
    iptables -D FORWARD "${linenums[$i]}" 2>/dev/null || true
    (( count++ )) || true
  done

  log::debug "flush_peer: removed $count FORWARD rules for: $client_ip"
}

function fw::apply_dns_redirect() {
  if iptables -t nat -C PREROUTING -s "$(config::subnet_for guest).0/24" -p udp --dport 53 -j DNAT --to-destination "$(config::dns):53" 2>/dev/null; then
    log::wg "Guest DNS redirect already applied"
    return 0
  fi

  local guest_subnet dns
  guest_subnet="$(config::subnet_for "guest").0/24"
  dns="$(config::dns)"

  # Log DNS bypass attempts (queries not directed at Pi-hole)
  iptables -t nat -A PREROUTING -s "$guest_subnet" -p udp --dport 53 \
    ! -d "$dns" \
    -j LOG --log-prefix "wgctl-dns-redirect: " --log-level 4
  iptables -t nat -A PREROUTING -s "$guest_subnet" -p tcp --dport 53 \
    ! -d "$dns" \
    -j LOG --log-prefix "wgctl-dns-redirect: " --log-level 4

  # Redirect all DNS to Pi-hole
  iptables -t nat -A PREROUTING -s "$guest_subnet" -p udp --dport 53 -j DNAT --to-destination "${dns}:53"
  iptables -t nat -A PREROUTING -s "$guest_subnet" -p tcp --dport 53 -j DNAT --to-destination "${dns}:53"

  log::wg_block "Guest DNS redirected to Pi-hole (${dns}), bypass attempts will be logged"
}

function fw::remove_dns_redirect() {
  local guest_subnet dns
  guest_subnet="$(config::subnet_for "guest").0/24"
  dns="$(config::dns)"

  iptables -t nat -D PREROUTING -s "$guest_subnet" -p udp --dport 53 \
    ! -d "$dns" \
    -j LOG --log-prefix "wgctl-dns-redirect: " --log-level 4 2>/dev/null || true
  iptables -t nat -D PREROUTING -s "$guest_subnet" -p tcp --dport 53 \
    ! -d "$dns" \
    -j LOG --log-prefix "wgctl-dns-redirect: " --log-level 4 2>/dev/null || true
  iptables -t nat -D PREROUTING -s "$guest_subnet" -p udp --dport 53 -j DNAT --to-destination "${dns}:53" 2>/dev/null || true
  iptables -t nat -D PREROUTING -s "$guest_subnet" -p tcp --dport 53 -j DNAT --to-destination "${dns}:53" 2>/dev/null || true

  log::debug "Removed guest DNS redirect"
}
function fw::nat_add_dns_redirect() {
  local subnet="${1:-}" dns="${2:-}" interface="${3:-wg0}"
  iptables -t nat -A PREROUTING -i "$interface" -s "$subnet" \
    -p udp --dport 53 ! -d "$dns" \
    -j LOG --log-prefix "wgctl-dns-redirect: " --log-level 4
  iptables -t nat -A PREROUTING -i "$interface" -s "$subnet" \
    -p udp --dport 53 -j DNAT --to-destination "${dns}:53"
  iptables -t nat -A PREROUTING -i "$interface" -s "$subnet" \
    -p tcp --dport 53 -j DNAT --to-destination "${dns}:53"
}

function fw::nat_remove_dns_redirect() {
  local subnet="${1:-}" dns="${2:-}" interface="${3:-wg0}"
  iptables -t nat -D PREROUTING -i "$interface" -s "$subnet" \
    -p udp --dport 53 ! -d "$dns" \
    -j LOG --log-prefix "wgctl-dns-redirect: " \
    --log-level 4 2>/dev/null || true
  iptables -t nat -D PREROUTING -i "$interface" -s "$subnet" \
    -p udp --dport 53 -j DNAT \
    --to-destination "${dns}:53" 2>/dev/null || true
  iptables -t nat -D PREROUTING -i "$interface" -s "$subnet" \
    -p tcp --dport 53 -j DNAT \
    --to-destination "${dns}:53" 2>/dev/null || true
}