#!/usr/bin/env bash

# ============================================
# Block file management
# ============================================

# ── Core state queries ─────────────────────

function block::file() {
  local name="${1:?name required}"
  ctx::block::path "${name}.block"
}

function block::has_file() {
  local name="${1:?}"
  [[ -f "$(block::file "$name")" ]]
}

function block::has_specific_rules() {
  local name="${1:?}"
  block::has_file "$name" || return 1
  while IFS="|" read -r bname btype target port proto; do
    [[ -z "$btype" ]] && continue
    [[ "$btype" != "full" ]] && return 0
  done < <(block::get_rules "$name")
  return 1
}

function block::is_blocked() {
  local name="${1:?}"
  block::has_file "$name" || return 1
  local result
  result=$(json::block_is_blocked "$(block::file "$name")")
  [[ "$result" == "true" ]]
}

function block::is_blocked_direct() {
  local name="${1:?}"
  block::has_file "$name" || { echo "false"; return 0; }
  json::block_get_direct "$(block::file "$name")"
}

function block::get_groups() {
  local name="${1:?}"
  block::has_file "$name" || return 0
  json::block_get_groups "$(block::file "$name")"
}

function block::get_rules() {
  local name="${1:?}"
  block::has_file "$name" || return 0
  json::block_get_rules "$(block::file "$name")"
}

# ── State mutations ────────────────────────

function block::set_direct() {
  local name="${1:?}" client_ip="${2:?}" value="${3:-true}"
  local file
  file=$(block::file "$name")
  json::block_set_direct "$file" "$client_ip" "$value"
}

function block::add_group() {
  local name="${1:?}" client_ip="${2:?}" group="${3:?}"
  local file
  file=$(block::file "$name")
  json::block_add_group "$file" "$client_ip" "$group"
}

function block::remove_group() {
  local name="${1:?}" client_ip="${2:?}" group="${3:?}"
  local file
  file=$(block::file "$name")
  json::block_remove_group "$file" "$client_ip" "$group"
}

function block::add_rule() {
  local name="${1:?}" client_ip="${2:?}"
  local rule_type="${3:?}" rule_name="${4:-}"
  local target="${5:-}" port="${6:-}" proto="${7:-}"
  local file
  file=$(block::file "$name")
  json::block_add_rule "$file" "$client_ip" "$rule_type" \
    "$rule_name" "$target" "$port" "$proto"
}

function block::remove_rule() {
  local name="${1:?}"
  local rule_type="${2:?}" target="${3:-}" port="${4:-}" proto="${5:-}"
  local file
  file=$(block::file "$name")
  json::block_remove_rule "$file" "$rule_type" "$target" "$port" "$proto"
}

function block::remove_file() {
  local name="${1:?}"
  rm -f "$(block::file "$name")"
}

function block::rename() {
  local name="${1:?}" new_name="${2:?}"
  local old_file new_file
  old_file=$(block::file "$name")
  new_file=$(block::file "$new_name")
  [[ -f "$old_file" ]] && mv "$old_file" "$new_file"

  return 0
}

function block::clear_full_block() {
  local name="${1:?}" 
  local file
  file=$(block::file "$name")
  [[ ! -f "$file" ]] && return 0
  json::block_remove_rule "$file" "full"
}

# ── High level operations ──────────────────

function block::apply_full() {
  local name="${1:?}" client_ip="${2:?}"

  fw::flush_peer "$client_ip"
  fw::block_all "$client_ip" "$name"
  block::add_rule "$name" "$client_ip" "full" "full block"

  local public_key endpoint
  public_key=$(keys::public "$name") || return 1
  endpoint=$(monitor::endpoint_for_key "$public_key")
  [[ -n "$endpoint" ]] && monitor::watch "$endpoint" "$name"
  peers::remove_from_server "$name"
  peers::reload
}


function block::restore_peer() {
  local name="${1:?}" client_ip="${2:?}"
  fw::unblock_all "$client_ip"
  fw::flush_peer "$client_ip"
  monitor::unwatch_client "$name"
  if ! peers::exists_in_server "$name"; then
    local public_key
    public_key=$(keys::public "$name") || return 1
    peers::add_to_server "$name" "$public_key" "$client_ip"
    peers::reload
  fi
  return 0
}

function block::restore_rules_for() {
  local name="${1:?}" client_ip="${2:?}"
  while IFS="|" read -r bname btype target port proto; do
    [[ -z "$btype" ]] && continue
    case "$btype" in
      ip)     fw::block_ip     "$client_ip" "$target"              "append" ;;
      port)   fw::block_port   "$client_ip" "$target" "$port" \
                               "${proto:-tcp}"                     "append" ;;
      subnet) fw::block_subnet "$client_ip" "$target"              "append" ;;
    esac
  done < <(block::get_rules "$name")
}

function block::restore_all() {
  while IFS= read -r peer_name; do
    block::has_file "$peer_name" || continue
    local client_ip
    client_ip=$(peers::get_ip "$peer_name")
    [[ -z "$client_ip" ]] && continue
    while IFS="|" read -r bname btype target port proto; do
      [[ -z "$btype" ]] && continue
      case "$btype" in
        full)   fw::block_all    "$client_ip" "$peer_name" ;;
        ip)     fw::block_ip     "$client_ip" "$target" ;;
        port)   fw::block_port   "$client_ip" "$target" "$port" "${proto:-tcp}" ;;
        subnet) fw::block_subnet "$client_ip" "$target" ;;
      esac
    done < <(block::get_rules "$peer_name")
  done < <(peers::all)
}

# ── Display helpers ────────────────────────

function block::format_rules() {
  local name="${1:?}"
  block::has_file "$name" || return 0

  while IFS="|" read -r bname btype target port proto; do
    [[ -z "$btype" ]] && continue

    local display="" ann=""

    case "$btype" in
      full)
        display="all traffic"
        ;;
      ip)
        display="$target"
        ann=$(net::annotate "$target")
        ;;
      port)
        display="${target}:${port}:${proto}"
        ann=$(net::annotate "${target}:${port}:${proto}")
        ;;
      subnet)
        display="$target"
        ann=$(net::annotate "${target%%/*}")
        ;;
    esac

    local label="$bname"

    # If bname wasn't set (equals type default), clear it
    case "$label" in
      full|ip|port|subnet|"") label="" ;;
    esac

    # Suppress label if it matches annotation
    if [[ -n "$ann" && -n "$label" && \
          ("$ann" == "$label" || "$ann" == "${label}:"*) ]]; then
      label=""
    fi

    # log::debug "label='$label' ann='$ann' match=$([ "$ann" == "$label" ] && echo yes || echo no)"

    printf "    \033[0;31m-\033[0m %-20s \033[0;37m%s%s\033[0m\n" \
      "$display" \
      "${label:+${label}  }" \
      "${ann:+→ ${ann}}"

  done < <(block::get_rules "$name")
  return 0
}

function block::cleanup() {
  local name="${1:?}"
  block::has_file "$name" || return 0
  local result
  result=$(json::block_is_empty "$(block::file "$name")")
  [[ "$result" == "true" ]] && block::remove_file "$name"
  return 0
}


# function block::format_rules() {
#   local name="${1:?}"
#   block::has_file "$name" || return 0
#   while IFS="|" read -r bname btype target port proto; do
#     [[ -z "$btype" ]] && continue
#     local display
#     case "$btype" in
#       full)   display="all traffic" ;;
#       ip)     display="$target" ;;
#       port)   display="${target}:${port}:${proto}" ;;
#       subnet) display="$target" ;;
#     esac
#     local label="${bname:-$btype}"
#     printf "    \033[0;31m-\033[0m %-30s \033[0;37m%s\033[0m\n" \
#       "$display" "$label"
#   done < <(block::get_rules "$name")
# }