#!/usr/bin/env bash
# commands/block/helpers.sh

# cmd::block::_impl <name> <identity> <type> <block_name> <reason>
#                   <quiet> <force>
#                   <ips_nameref> <subnets_nameref> <ports_nameref> <services_nameref>
function cmd::block::_impl() {
  local name="$1" identity="$2" type="$3" block_name="$4" reason="$5"
  local quiet="$6" force="$7"
  local -n _ips_ref="$8"
  local -n _subnets_ref="$9"
  local -n _ports_ref="${10}"
  local -n _services_ref="${11}"

  # --identity: block all peers for this identity
  if [[ -n "$identity" ]]; then
    cmd::block::_block_identity "$identity" "$quiet" \
      "${_ips_ref[@]+"${_ips_ref[@]}"}" || return 1
    return 0
  fi

  [[ -z "$name" ]] && {
    log::error "Missing required flag: --name or --identity"
    return 1
  }

  name=$(peers::resolve_and_require "$name" "$type") || return 1

  local client_ip
  client_ip=$(peers::get_ip "$name") || return 1

  # Full block if no specific targets
  if [[ ${#_ips_ref[@]} -eq 0 && ${#_ports_ref[@]} -eq 0 && \
        ${#_subnets_ref[@]} -eq 0 && ${#_services_ref[@]} -eq 0 ]]; then
    if peers::is_blocked "$name"; then
      log::wg_warning "Client is already blocked: ${name}"
      return 0
    fi
    monitor::update_endpoint_cache
    cmd::block::_block_all "$name" "$client_ip" "$quiet"
    cmd::block::_record_history "$name" "full" "manual" "$reason"
    return 0
  fi

  # Specific rules
  if block::has_file "$name"; then
    local direct
    direct=$(block::is_blocked_direct "$name")
    if [[ "$direct" == "true" ]]; then
      log::wg_warning "${name} is fully blocked — unblock first to add specific rules"
      return 1
    fi
  fi

  local changed=false

  for ip in "${_ips_ref[@]:-}"; do
    ip::require_valid "$ip"
    fw::block_ip "$client_ip" "$ip"
    block::add_rule "$name" "$client_ip" "ip" "${block_name:-}" "$ip"
    $quiet || log::wg_success "${ip} has been blocked for ${name}"
  done

  for subnet in "${_subnets_ref[@]:-}"; do
    ip::require_valid "$subnet"
    fw::block_subnet "$client_ip" "$subnet"
    block::add_rule "$name" "$client_ip" "subnet" "${block_name:-}" "$subnet"
    $quiet || log::wg_success "${subnet} has been blocked for ${name}"
  done

  for entry in "${_ports_ref[@]:-}"; do
    local b_target b_port b_proto
    IFS=":" read -r b_target b_port b_proto <<< "$entry"
    ip::require_valid "$b_target"
    fw::block_port "$client_ip" "$b_target" "$b_port" "${b_proto:-tcp}"
    block::add_rule "$name" "$client_ip" "port" "${block_name:-}" \
      "$b_target" "$b_port" "${b_proto:-tcp}"
    $quiet || log::wg_success "${b_target}:${b_port}:${b_proto:-tcp} has been blocked for ${name}"
  done

  for svc in "${_services_ref[@]:-}"; do
    local resolved_lines=()
    mapfile -t resolved_lines < <(net::resolve "$svc" 2>/dev/null)
    if [[ ${#resolved_lines[@]} -eq 0 ]]; then
      log::error "Service not found or has no ports: ${svc}"
      return 1
    fi

    local already_blocked=true
    for resolved in "${resolved_lines[@]}"; do
      if [[ "$resolved" == *:*:* ]]; then
        local b_ip b_port b_proto
        IFS=":" read -r b_ip b_port b_proto <<< "$resolved"
        fw::has_block_rule "$client_ip" "$b_ip" "$b_port" "$b_proto" 2>/dev/null || \
          { already_blocked=false; break; }
      else
        fw::has_block_rule "$client_ip" "$resolved" 2>/dev/null || \
          { already_blocked=false; break; }
      fi
    done

    if $already_blocked; then
      $quiet || log::wg_warning "${svc} is already blocked for ${name}"
      continue
    fi

    for resolved in "${resolved_lines[@]}"; do
      if [[ "$resolved" == *:*:* ]]; then
        local b_ip b_port b_proto
        IFS=":" read -r b_ip b_port b_proto <<< "$resolved"
        fw::block_port "$client_ip" "$b_ip" "$b_port" "$b_proto"
        block::add_rule "$name" "$client_ip" "port" "$svc" \
          "$b_ip" "$b_port" "$b_proto"
      else
        fw::block_ip "$client_ip" "$resolved"
        block::add_rule "$name" "$client_ip" "ip" "$svc" "$resolved"
      fi
    done

    changed=true
    $quiet || log::wg_success "${svc} has been blocked for ${name}"
  done

  [[ ${#_ips_ref[@]} -gt 0 || ${#_ports_ref[@]} -gt 0 || \
     ${#_subnets_ref[@]} -gt 0 ]] && changed=true

  if $changed; then
    local peer_rule
    peer_rule=$(peers::get_meta "$name" "rule")
    if [[ -n "$peer_rule" ]] && rule::exists "$peer_rule"; then
      fw::flush_peer "$client_ip"
      rule::apply "$peer_rule" "$client_ip" "$name"
      block::restore_rules_for "$name" "$client_ip"
    fi
  fi

  local btype="specific"
  [[ ${#_services_ref[@]} -gt 0 ]] && btype="${_services_ref[0]}"
  [[ ${#_ips_ref[@]} -gt 0 ]]      && btype="ip"
  [[ ${#_subnets_ref[@]} -gt 0 ]]  && btype="subnet"
  [[ ${#_ports_ref[@]} -gt 0 ]]    && btype="port"
  cmd::block::_record_history "$name" "$btype" "manual" "$reason"

  return 0
}

# ── Helpers ───────────────────────────────────────────────────────────────────

function cmd::block::_block_identity() {
  local identity_name="${1:-}" quiet="${2:-false}"
  shift 2 || true

  identity::require_exists "$identity_name" || return 1
  identity::require_has_peers "$identity_name" || return 1

  local peers blocked=0 failed=0
  peers=$(identity::peers "$identity_name")

  while IFS= read -r peer_name; do
    [[ -z "$peer_name" ]] && continue
    if peers::is_blocked "$peer_name"; then
      $quiet || log::wg_warning "${peer_name} is already blocked"
      continue
    fi
    local client_ip
    client_ip=$(peers::get_ip "$peer_name") || continue
    monitor::update_endpoint_cache
    if cmd::block::_block_all "$peer_name" "$client_ip" true; then
      blocked=$(( blocked + 1 ))
    else
      failed=$(( failed + 1 ))
    fi
  done <<< "$peers"

  log::ok "Blocked ${blocked} peer(s) for identity '${identity_name}'"
  [[ $failed -gt 0 ]] && log::warn "${failed} peer(s) failed to block"
  return 0
}

function cmd::block::_get_endpoint() {
  local name="$1" public_key="$2"
  local endpoint
  endpoint=$(monitor::endpoint_for_key "$public_key")
  if [[ -z "$endpoint" || "$endpoint" == "(none)" ]]; then
    endpoint=$(monitor::get_cached_endpoint "$name")
  fi
  echo "$endpoint"
}

function cmd::block::_block_all() {
  local name="${1:?name required}"
  local client_ip="${2:?client_ip required}"
  local quiet="${3:-false}"

  block::apply_full "$name" "$client_ip"
  block::set_direct "$name" "$client_ip" "true"

  $quiet || log::wg_success "${name} has been blocked."
}

function cmd::block::_record_history() {
  local name="${1:-}" block_type="${2:-full}" \
        triggered_by="${3:-manual}" reason="${4:-}" 

  local endpoint
  endpoint=$(json::peer_history_lookup "$name" 2>/dev/null || true)

  # endpoint_cache lookup
  local ep_cache
  ep_cache=$(json::endpoint_cache_get "$(ctx::endpoint_cache)" "$name" 2>/dev/null || true)
 
  json::block_history_record \
    "$(ctx::block_history)" \
    "$name" \
    "$block_type" \
    "$triggered_by" \
    "$reason" \
    "${ep_cache:-}" \
    2>/dev/null > /dev/null || true
}