#!/usr/bin/env bash

# ============================================
# Lifecycle
# ============================================

function cmd::group::on_load() {
  flag::register --name
  flag::register --desc
  flag::register --peer
  flag::register --type
  flag::register --rule
  flag::register --new-name
  flag::register --main
  flag::register --force
  flag::register --all
  flag::register --dry-run
  command::mixin json_output
}

# ============================================
# Help
# ============================================

function cmd::group::help() {
  cat <<EOF
Usage: wgctl group <subcommand> [options]

Manage peer groups. Operations like block/unblock act on all peers in a group.
A peer can belong to multiple groups (M:N relationship).
Group blocks track which groups blocked a peer — unblocking one group won't
unblock a peer still blocked by another group.

Subcommands:
  list, ls                    List all groups
  show                        Show group members and their status
  add, new, create            Create a new group
  remove, rm, del             Remove a group definition
  rename                      Rename a group
  peer add                    Add a peer to a group
  peer remove, peer rm        Remove a peer from a group
  rm-peers                    Remove all peers in group from WireGuard
  purge-stale                 Remove peers that no longer exist from group(s)
  block                       Block all peers in group
  unblock                     Unblock all peers in group
  rule assign                 Assign a rule to all peers in group
  audit                       Audit firewall rules for all peers in group
  logs                        Show activity logs for all peers in group
  watch                       Live monitor for all peers in group

Options:
  --name <name>               Group name
  --desc <description>        Group description (for add)
  --peer <peer>               Peer name
  --type <type>               Peer device type (optional)
  --rule <rule>               Rule name (for rule assign)
  --new-name <name>           New group name (for rename)
  --limit <n>                 Max log entries per peer (for logs)
  --force                     Skip confirmation prompts
  --all                       Apply to all groups (for purge-stale)

Examples:
  wgctl group list
  wgctl group add --name family --desc "Family devices"
  wgctl group peer add --name family --peer phone-nuno
  wgctl group show --name family
  wgctl group block --name family
  wgctl group unblock --name family
  wgctl group rule assign --name family --rule user
  wgctl group purge-stale --name family
  wgctl group purge-stale --all
  wgctl group purge-stale --all --force
  wgctl group audit --name family
  wgctl group logs --name family --limit 20
  wgctl group watch --name family
EOF
}

# ============================================
# Run
# ============================================

function cmd::group::run() {
  local subcmd="${1:-help}"
  shift || true

  if command::json; then
    cmd::group::_output_json
    return 0
  fi

  case "$subcmd" in
    list|ls)              cmd::group::list "$@"        ;;
    show)                 cmd::group::show "$@"        ;;
    add|new|create)       cmd::group::add "$@"         ;;
    remove|rm|del|delete) cmd::group::remove "$@"      ;;
    rename)               cmd::group::rename "$@"      ;;
    peer)                 cmd::group::peer "$@"        ;;
    rm-peers)             cmd::group::rm_peers "$@"    ;;
    set-main)             cmd::group::set_main "$@"    ;;
    block)                cmd::group::block "$@"       ;;
    unblock)              cmd::group::unblock "$@"     ;;
    rule)                 cmd::group::rule "$@"        ;;
    purge-stale)          cmd::group::purge_stale "$@" ;;
    audit)                cmd::group::audit "$@"       ;;
    logs)                 cmd::group::logs "$@"        ;;
    watch)                cmd::group::watch "$@"       ;;
    help)                 cmd::group::help             ;;
    *)
      log::error "Unknown subcommand: '${subcmd}'"
      cmd::group::help
      return 1
      ;;
  esac
}

# ============================================
# List
# ============================================

function cmd::group::list() {
  local groups_dir
  groups_dir="$(ctx::groups)"
 
  local groups=("${groups_dir}"/*.group)
  if [[ ! -f "${groups[0]}" ]]; then
    log::wg "No groups configured"
    return 0
  fi
 
  local data
  data=$(json::group_list_data "$groups_dir" "$(ctx::blocks)" "$(ctx::clients)")
  [[ -z "$data" ]] && log::wg "No groups configured" && return 0
 
  # Measure column widths
  local w_name=12 w_desc=16
  while IFS="|" read -r name desc total blocked; do
    [[ -z "$name" ]] && continue
    (( ${#name} > w_name )) && w_name=${#name}
    local desc_len=${#desc}
    [[ -z "$desc" ]] && desc_len=1
    (( desc_len > w_desc )) && w_desc=$desc_len
  done <<< "$data"
  (( w_name += 2 ))
  (( w_desc += 2 ))
 
  log::section "Groups"
  echo ""
 
  if display::is_table "group_list"; then
    cmd::group::_render_table "$data" "$w_name" "$w_desc"
    return 0
  fi

  while IFS="|" read -r name desc total blocked; do
    [[ -z "$name" ]] && continue
    ui::group::list_row "$name" "$desc" "$total" "$blocked" "$w_name" "$w_desc"
  done <<< "$data"
 
  echo ""
}

# ============================================
# Show
# ============================================

function cmd::group::show() {
  local name=""

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
      --help) cmd::group::help; return ;;
      *) log::error "Unknown flag: $1"; return 1 ;;
    esac
  done

  [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
  group::require_exists "$name" || return 1

  local group_file
  group_file="$(group::path "$name")"

  log::section "Group: ${name}"
  printf "\n"

  local desc
  desc=$(json::get "$group_file" "desc")
  ui::row "Description" "${desc:-—}"

  # Load and filter peers
  local peers_list=()
  mapfile -t peers_list < <(json::get "$group_file" "peers") || true
  local filtered=()
  for p in "${peers_list[@]:-}"; do
    [[ -n "$p" ]] && filtered+=("$p")
  done
  peers_list=("${filtered[@]:-}")
  local peer_count=${#peers_list[@]}
  [[ -z "${peers_list[0]:-}" ]] && peer_count=0

  # Count valid peers (data logic stays in command)
  local valid_count=0
  for p in "${peers_list[@]}"; do
    [[ -z "$p" ]] && continue
    peers::require_exists "$p" > /dev/null 2>&1 && (( valid_count++ )) || true
  done
  local peer_word="peers"
  [[ "$valid_count" -eq 1 ]] && peer_word="peer"
  ui::row "Peers" "${valid_count} ${peer_word}"
  printf "\n"

  if [[ "$peer_count" -gt 0 ]]; then
    # Measure widths (data logic stays in command)
    local w_name=16 w_ip=13
    for peer_name in "${peers_list[@]}"; do
      [[ -z "$peer_name" ]] && continue
      (( ${#peer_name} > w_name )) && w_name=${#peer_name}
    done
    (( w_name += 2 ))

    # Delegate rendering to ui::
    ui::group::show_peers peers_list "$w_name" "$w_ip"
  else
    printf "  \033[2m—\033[0m\n"
  fi

  printf "\n"
}

function cmd::group::_render_table() {
  local data="${1:-}" w_name="${2:-20}" w_desc="${3:-20}"
  [[ -z "$data" ]] && return 0
 
  ui::group::list_header_table
  while IFS='|' read -r name desc total blocked; do
    [[ -z "$name" ]] && continue
    ui::group::list_row_table "$name" "$desc" "$total" "$blocked"
  done <<< "$data"
}

# ============================================
# Add
# ============================================

function cmd::group::add() {
  local name="" desc=""

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
      --desc) util::require_flag "--desc" "${2:-}" || return 1; desc="$2"; shift 2 ;;
      --help) cmd::group::help; return ;;
      *) log::error "Unknown flag: $1"; return 1 ;;
    esac
  done

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

  if group::exists "$name"; then
    log::error "Group already exists: ${name}"
    return 1
  fi

  json::create_group "$(group::path "$name")" "$name" "$desc"

  log::wg_success "Group created: ${name}"
}

# ============================================
# Remove
# ============================================

function cmd::group::remove() {
  local name="" force=false

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name)  util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
      --force) force=true; shift ;;
      --help)  cmd::group::help; return ;;
      *) log::error "Unknown flag: $1"; return 1 ;;
    esac
  done

  [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
  group::require_exists "$name" || return 1

  if ! $force; then
    read -r -p "Remove group '${name}'? This only removes the group definition, not the peers. [y/N] " confirm
    case "$confirm" in
      [yY][eE][sS]|[yY]) ;;
      *) log::info "Aborted"; return 0 ;;
    esac
  fi

  rm -f "$(group::path "$name")"
  log::wg_success "Group removed: ${name}"
}

# ============================================
# Rename
# ============================================

function cmd::group::rename() {
  local name="" new_name=""

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name)     util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
      --new-name) util::require_flag "--new-name" "${2:-}" || return 1; new_name="$2"; shift 2 ;;
      --help) cmd::group::help; return ;;
      *) log::error "Unknown flag: $1"; return 1 ;;
    esac
  done

  [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
  [[ -z "$new_name" ]] && log::error "Missing required flag: --new-name" && return 1
  group::require_exists "$name" || return 1

  if group::exists "$new_name"; then
    log::error "Group already exists: ${new_name}"
    return 1
  fi

  local old_file new_file
  old_file="$(group::path "$name")"
  new_file="$(group::path "$new_name")"

  # Update name field in file
  json::set "$old_file" "name" "\"$new_name\""
  mv "$old_file" "$new_file"

  log::wg_success "Group renamed: ${name} → ${new_name}"
}

# ============================================
# Peer subcommand
# ============================================

function cmd::group::peer() {
  local subcmd="${1:-help}"
  shift || true

  case "$subcmd" in
    add)           cmd::group::peer_add "$@"    ;;
    remove|rm|del) cmd::group::peer_remove "$@" ;;
    *)
      log::error "Unknown peer subcommand: '${subcmd}'"
      cmd::group::help
      return 1
      ;;
  esac
}

function cmd::group::peer_add() {
  local name="" peer="" type="" set_main=false

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
      --peer) util::require_flag "--peer" "${2:-}" || return 1; peer="$2"; shift 2 ;;
      --type) util::require_flag "--type" "${2:-}" || return 1; type="$2"; shift 2 ;;
      --main) util::require_flag "--main" "${2:-}" || return 1; set_main=true; shift ;;
      --help) cmd::group::help; return ;;
      *) log::error "Unknown flag: $1"; return 1 ;;
    esac
  done

  [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
  [[ -z "$peer" ]] && log::error "Missing required flag: --peer" && return 1
  group::require_exists "$name" || return 1

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

  # Check if already in group
  if group::peers "$name" | grep -qF "$peer"; then
    log::wg_warning "'${peer}' is already in group '${name}'"
    return 0
  fi

  group::add_peer "$name" "$peer"
  log::wg_success "Added '${peer}' to group '${name}'"

  if $set_main; then
    peers::set_main_group "$peer_name" "$group_name"
    log::wg_success "Set '${group_name}' as main group for ${peer_name}"
  fi
}

function cmd::group::peer_remove() {
  local name="" peer="" type=""

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
      --peer) util::require_flag "--peer" "${2:-}" || return 1; peer="$2"; shift 2 ;;
      --type) util::require_flag "--type" "${2:-}" || return 1; type="$2"; shift 2 ;;
      --help) cmd::group::help; return ;;
      *) log::error "Unknown flag: $1"; return 1 ;;
    esac
  done

  [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
  [[ -z "$peer" ]] && log::error "Missing required flag: --peer" && return 1
  group::require_exists "$name" || return 1

  peer=$(peers::resolve_and_require "$peer" "$type") || return 1
  group::remove_peer "$name" "$peer"
  log::wg_success "Removed '${peer}' from group '${name}'"
}

function cmd::group::set_main() {
  local group_name="" peer_name="" type=""
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name) group_name="$2"; shift 2 ;;
      --peer) peer_name="$2";  shift 2 ;;
      --type) type="$2";       shift 2 ;;
      *) log::error "Unknown flag: $1"; return 1 ;;
    esac
  done

  [[ -z "$group_name" ]] && log::error "Missing --name" && return 1
  [[ -z "$peer_name"  ]] && log::error "Missing --peer" && return 1

  # Resolve peer name
  peer_name=$(peers::resolve_and_require "$peer_name" "$type") || return 1

  # Verify peer is in the group
  if ! group::has_peer "$group_name" "$peer_name"; then
    log::error "Peer '${peer_name}' is not in group '${group_name}'"
    log::info  "Add them first: wgctl group peer add --name ${group_name} --peer ${peer_name}"
    return 1
  fi

  peers::set_main_group "$peer_name" "$group_name"
  log::wg_success "Main group for '${peer_name}' set to '${group_name}'"
}

# ============================================
# Remove peers from WireGuard
# ============================================

function cmd::group::rm_peers() {
  local name="" force=false

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name)  util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
      --force) force=true; shift ;;
      --help)  cmd::group::help; return ;;
      *) log::error "Unknown flag: $1"; return 1 ;;
    esac
  done

  [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
  group::require_exists "$name" || return 1

  local peers_list=()
  mapfile -t peers_list < <(group::peers "$name")
  local peer_count=${#peers_list[@]}
  [[ -z "${peers_list[0]:-}" ]] && peer_count=0

  if [[ "$peer_count" -eq 0 ]]; then
    log::wg_warning "Group '${name}' has no peers"
    return 0
  fi

  if ! $force; then
    read -r -p "Remove all ${peer_count} peers in group '${name}' from WireGuard? [y/N] " confirm
    case "$confirm" in
      [yY][eE][sS]|[yY]) ;;
      *) log::info "Aborted"; return 0 ;;
    esac
  fi

  load_command remove
  group::each_peer "$name" cmd::group::_rm_peer_cb
  log::wg_success "Removed peers from group '${name}' (definition kept)"
}

function cmd::group::_rm_peer_cb() {
  local peer_name="${1:-}"
  if ! group::_peer_exists_check "$peer_name"; then
    log::wg_warning "Peer '${peer_name}' no longer exists — skipping"
    return 0
  fi
  cmd::remove::run --name "$peer_name" --force
}

# ============================================
# Block / Unblock
# ============================================

function cmd::group::block() {
  local name="" force=false

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name)  util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
      --force) force=true; shift ;;
      --help)  cmd::group::help; return ;;
      *) log::error "Unknown flag: $1"; return 1 ;;
    esac
  done

  [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
  group::require_exists "$name" || return 1

  local peers_list=()
  mapfile -t peers_list < <(group::peers "$name")
  
  if [[ ${#peers_list[@]} -eq 0 ]] || [[ -z "${peers_list[0]:-}" ]]; then
    log::wg_warning "Group '${name}' has no peers"
    return 0
  fi

  local count=0 skipped=0 blocked_names=()
  local filtered=()
  for p in "${peers_list[@]:-}"; do
    [[ -n "$p" ]] && filtered+=("$p")
  done
  [[ ${#filtered[@]} -eq 0 ]] && log::wg_warning "Group '${name}' has no peers" && return 0

  for peer_name in "${filtered[@]}"; do
    if cmd::group::_block_peer "$peer_name" "$name"; then
      (( count++ )) || true
    else
      (( skipped++ )) || true
    fi
  done

  if [[ "$count" -gt 0 ]]; then
    log::wg_block "All peers from ${name} have been blocked (${count} peers)."
  fi

  if [[ "$skipped" -gt 0 ]]; then
    log::wg_warning "${skipped} peers already blocked"
  fi
}

function cmd::group::unblock() {
  local name="" force=false

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name)  util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
      --force) force=true; shift ;;
      --help)  cmd::group::help; return ;;
      *) log::error "Unknown flag: $1"; return 1 ;;
    esac
  done

  [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
  group::require_exists "$name" || return 1

  local peers_list=()
  mapfile -t peers_list < <(group::peers "$name")

  local filtered=()
  for p in "${peers_list[@]:-}"; do [[ -n "$p" ]] && filtered+=("$p"); done
  [[ ${#filtered[@]} -eq 0 ]] && log::wg_warning "Group '${name}' has no peers" && return 0

  local count=0 skipped=0

  for peer_name in "${filtered[@]}"; do
    if cmd::group::_unblock_peer "$peer_name" "$name"; then
      (( count++ )) || true
    else
      (( skipped++ )) || true
    fi
  done

  if [[ "$count" -gt 0 ]]; then
    log::wg_unblock "All peers from ${name} have been unblocked."
  fi

  if [[ "$skipped" -gt 0 ]]; then
    log::wg_warning "${skipped} peer(s) remain blocked (blocked directly or by other groups)"
  fi
}

function cmd::group::_block_peer() {
  local peer_name="${1:-}" group_name="${2:-}"
  if ! group::_peer_exists_check "$peer_name"; then
    log::wg_warning "Peer '${peer_name}' no longer exists — skipping"
    return 0
  fi

  local client_ip
  client_ip=$(peers::get_ip "$peer_name")

  # Check if already blocked by this group
  local current_blocked_groups
  current_blocked_groups=$(block::get_groups "$peer_name")

  local IFS=','
  for g in $current_blocked_groups; do
    if [[ "$g" == "$group_name" ]]; then
      log::wg_warning "${peer_name} — already blocked by group '${group_name}'"
      return 1
    fi
  done

  # Add group to block tracking
  block::add_group "$peer_name" "$client_ip" "$group_name"

  # Apply fw rules only if peer is still in WG server (not yet blocked)
  if peers::exists_in_server "$peer_name"; then
    block::apply_full "$peer_name" "$client_ip"
  fi
}

function cmd::group::_unblock_peer() {
  local peer_name="${1:-}" group_name="${2:-}"

  if ! group::_peer_exists_check "$peer_name"; then
    log::wg_warning "Peer '${peer_name}' no longer exists — skipping"
    return 1
  fi

  # Check if blocked by this group at all
  if ! block::has_file "$peer_name"; then
    log::wg_warning "${peer_name} — not blocked"
    return 1
  fi

  local current_groups
  current_groups=$(block::get_groups "$peer_name")
  if [[ "$current_groups" != *"$group_name"* ]]; then
    log::wg_warning "${peer_name} — not blocked by group '${group_name}'"
    return 1
  fi

  local client_ip
  client_ip=$(peers::get_ip "$peer_name")

  block::remove_group "$peer_name" "$client_ip" "$group_name"

  if block::is_blocked "$peer_name"; then
    local groups
    groups=$(block::get_groups "$peer_name")

    local direct
    direct=$(block::is_blocked_direct "$peer_name")

    if [[ "$direct" == "true" ]]; then
      log::wg_warning "${peer_name} — still blocked directly, skipping"
    else
      log::wg_warning "${peer_name} — still blocked by group(s): ${groups}, skipping"
    fi

    return 1
  fi

  block::restore_peer "$peer_name" "$client_ip"
  block::remove_file "$peer_name"

  local rule
  rule=$(peers::get_meta "$peer_name" "rule")

  [[ -n "$rule" ]] && rule::exists "$rule" && \
    rule::apply "$rule" "$client_ip" "$peer_name"
}

# ============================================
# Rule assign
# ============================================

function cmd::group::rule() {
  local subcmd="${1:-help}"
  shift || true
  case "$subcmd" in
    assign) cmd::group::rule_assign "$@" ;;
    *)
      log::error "Unknown rule subcommand: '${subcmd}'"
      cmd::group::help
      return 1
      ;;
  esac
}

function cmd::group::rule_assign() {
  local name="" rule=""

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
      --rule) util::require_flag "--rule" "${2:-}" || return 1; rule="$2"; shift 2 ;;
      --help) cmd::group::help; return ;;
      *) log::error "Unknown flag: $1"; return 1 ;;
    esac
  done

  [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
  [[ -z "$rule" ]] && log::error "Missing required flag: --rule" && return 1
  group::require_exists "$name" || return 1
  rule::require_exists "$rule"  || return 1

  local peers_list=()
  mapfile -t peers_list < <(group::peers "$name")
  [[ -z "${peers_list[0]:-}" ]] && log::wg_warning "Group '${name}' has no peers" && return 0

  group::each_peer "$name" cmd::group::_rule_assign_cb "$rule"
  log::wg_success "Assigned rule '${rule}' to group '${name}'"
}

function cmd::group::_rule_assign_cb() {
  local peer_name="${1:-}" rule="${2:-}"
  if ! group::_peer_exists_check "$peer_name"; then
    log::wg_warning "Peer '${peer_name}' no longer exists — skipping"
    return 0
  fi
  load_command rule
  cmd::rule::assign --name "$rule" --peer "$peer_name"
}

# ============================================
# Audit
# ============================================

function cmd::group::audit() {
  local name=""

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
      --help) cmd::group::help; return ;;
      *) log::error "Unknown flag: $1"; return 1 ;;
    esac
  done

  [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
  group::require_exists "$name" || return 1

  local peers_list=()
  mapfile -t peers_list < <(group::peers "$name")
  [[ -z "${peers_list[0]}" ]] && log::wg_warning "Group '${name}' has no peers" && return 0

  # Run audit filtered to group peers
  load_command audit
  # Pass first peer as filter — audit handles the rest per-peer
  # Actually run full audit and filter output
  local peer_args=()
  for peer_name in "${peers_list[@]}"; do
    [[ -z "$peer_name" ]] && continue
    peer_args+=("$peer_name")
  done

  test::reset
  log::section "Audit: Group '${name}'"

  # Precompute fw counts
  declare -A peer_fw_counts
  while IFS=":" read -r pname count; do
    [[ -n "$pname" ]] && peer_fw_counts["$pname"]="$count"
  done < <(json::audit_fw_counts "$(ctx::clients)")

  test::section "Peer Rules"
  for peer_name in "${peer_args[@]}"; do
    # Skip if peer no longer exists
    if ! peers::require_exists "$peer_name" > /dev/null 2>&1; then
      log::wg_warning "Peer '${peer_name}' no longer exists — skipping"
      continue
    fi
    cmd::audit::check_peer "$peer_name" false "${peer_fw_counts[$peer_name]:-0}"
  done

  test::summary
}

# ============================================
# Logs
# ============================================

function cmd::group::logs() {
  local name="" limit=50

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name)  util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
      --limit) util::require_flag "--limit" "${2:-}" || return 1; limit="$2"; shift 2 ;;
      --help)  cmd::group::help; return ;;
      *) log::error "Unknown flag: $1"; return 1 ;;
    esac
  done

  [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
  group::require_exists "$name" || return 1

  local peers_list=()
  mapfile -t peers_list < <(group::peers "$name")
  [[ -z "${peers_list[0]}" ]] && log::wg_warning "Group '${name}' has no peers" && return 0

  log::section "Logs: Group '${name}'"

  for peer_name in "${peers_list[@]}"; do
    [[ -z "$peer_name" ]] && continue
    # Skip if peer no longer exists
    if ! peers::require_exists "$peer_name" > /dev/null 2>&1; then
      log::wg_warning "Peer '${peer_name}' no longer exists — skipping"
      continue
    fi
    printf "\n  \033[1;37m── %s ──\033[0m\n" "$peer_name"
    load_command logs
    cmd::logs::show --name "$peer_name" --limit "$limit"
  done
}

# ============================================
# Watch
# ============================================

function cmd::group::watch() {
  local name=""

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name) util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
      --help) cmd::group::help; return ;;
      *) log::error "Unknown flag: $1"; return 1 ;;
    esac
  done

  [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
  group::require_exists "$name" || return 1

  local peers_list=()
  mapfile -t peers_list < <(group::peers "$name")
  [[ -z "${peers_list[0]}" ]] && log::wg_warning "Group '${name}' has no peers" && return 0

  local peer_filter
  peer_filter=$(IFS=','; echo "${peers_list[*]}")

  # Build comma-separated peer list for watch filter
  # Watch already supports --type filter but not multiple peers
  # For now, use follow mode filtered to group peers one at a time
  # or just run watch with no filter (shows all, user sees group context)
  log::section "Live Monitor: Group '${name}'"
  printf "  Monitoring: %s\n\n" "${peers_list[*]}"

  load_command watch
  cmd::watch::run --peers "$peer_filter"
}

# ============================================
# Purge Stale
# ============================================

function cmd::group::purge_stale() {
  local name="" force=false all=false
  local dry_run=false

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name)  util::require_flag "--name" "${2:-}" || return 1; name="$2"; shift 2 ;;
      --force) force=true;  shift ;;
      --all)   all=true;    shift ;;
      --dry-run) dry_run=true; shift ;;
      --help)  cmd::group::help; return ;;
      *) log::error "Unknown flag: $1"; return 1 ;;
    esac
  done
 
  [[ -z "$name" && "$all" == "false" ]] && \
    log::error "Specify --name <group> or --all" && return 1
 
  # Build list of groups to process
  local -a groups=()
  if $all; then
    while IFS= read -r group_file; do
      groups+=("$(basename "$group_file" .group)")
    done < <(find "$(ctx::groups)" -name "*.group" 2>/dev/null | sort)
  else
    group::require_exists "$name" || return 1
    groups=("$name")
  fi
 
  local total_removed=0 total_groups=0
 
  for group_name in "${groups[@]}"; do
    [[ -z "$group_name" ]] && continue
 
    # Find stale peers — in group but no .conf file
    local -a stale=()
    while IFS= read -r peer_name; do
      [[ -z "$peer_name" ]] && continue
      if [[ ! -f "$(ctx::clients)/${peer_name}.conf" ]]; then
        stale+=("$peer_name")
      fi
    done < <(group::peers "$group_name" 2>/dev/null)
 
    [[ ${#stale[@]} -eq 0 ]] && continue
 
    (( total_groups++ )) || true
 
    if ! $force; then
      printf "  Group '%s' has %d stale peer(s): %s\n" \
        "$group_name" "${#stale[@]}" "${stale[*]}"
      read -r -p "  Remove them? [y/N] " confirm
      case "$confirm" in
        [yY]*) ;;
        *) log::info "Skipped '${group_name}'"; continue ;;
      esac
    fi
 
    local group_file
    group_file="$(group::path "$group_name")"
    for peer_name in "${stale[@]}"; do
      if $dry_run; then
        printf "  \033[2m[dry-run]\033[0m Would remove '%s' from group '%s'\n" \
          "$peer_name" "$group_name"
      else
        json::remove "$group_file" "peers" "$peer_name" 2>/dev/null || true
        log::debug "Removed stale peer '${peer_name}' from group '${group_name}'"
      fi
      (( total_removed++ )) || true
    done
  done
 
  local action="Removed"
  $dry_run && action="Would remove"
  log::wg_success "${action} ${total_removed} stale peer(s)..."

  if $all; then
    if [[ "$total_removed" -eq 0 ]]; then
      log::wg_warning "No stale peers found in any group"
    else
      log::wg_success "${action} ${total_removed} stale peer(s)..."
    fi
  fi
}

function cmd::group::_output_json() {
  local groups_dir
  groups_dir="$(ctx::groups)"
  local data
  data=$(json::group_list_data "$groups_dir" "$(ctx::blocks)" "$(ctx::clients)" 2>/dev/null)
 
  local -a groups=()
  while IFS='|' read -r name desc peer_count blocked_count; do
    [[ -z "$name" ]] && continue
    groups+=("$(printf '{"name":"%s","desc":"%s","peer_count":%s,"blocked_count":%s}' \
      "$name" "$desc" "$peer_count" "$blocked_count")")
  done <<< "$data"
 
  local count=${#groups[@]}
  local array
  array=$(printf '%s\n' "${groups[@]:-}" | paste -sd ',' -)
  printf '{"groups":[%s]}' "${array:-}" | json::envelope "group list" "$count"
}