#!/usr/bin/env bash

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

function cmd::list::on_load() {
  flag::register --type
  flag::register --rule
  flag::register --group
  flag::register --online
  flag::register --offline
  flag::register --restricted
  flag::register --blocked
  flag::register --allowed
  flag::register --detailed
  flag::register --name
}

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

function cmd::list::help() {
  cat <<EOF
Usage: wgctl list [options]

List all WireGuard clients.

Options:
  --type <type>       Filter by device type (desktop, laptop, phone, tablet)
  --rule <rule>       Filter by assigned rule
  --group <group>     Filter by group membership
  --online            Show only connected clients
  --offline           Show only disconnected clients
  --blocked           Show only fully blocked clients (removed from WireGuard)
  --restricted        Show only restricted clients (specific IP/port blocks applied)
  --allowed           Show only unrestricted clients
  --detailed          Show full detail cards for all clients
  --name <name>       Show detail card for a single client

Status values:
  online              Connected (recent handshake)
  offline             Not connected
  blocked             Removed from WireGuard server (wgctl block --name)
  restricted          In WireGuard but with specific access rules (wgctl block --ip/--service)

Examples:
  wgctl list
  wgctl list --type phone
  wgctl list --rule user
  wgctl list --group family
  wgctl list --online
  wgctl list --blocked
  wgctl list --restricted
  wgctl list --detailed
  wgctl list --name phone-nuno
EOF
}

# ============================================
# Header / Footer
# ============================================

function cmd::list::_render_header() {
  local has_groups="$1"
  if $has_groups; then
    printf "\n  %-28s %-15s %-13s %-12s %-12s %-22s %s\n" \
      "NAME" "IP" "TYPE" "RULE" "GROUP" "STATUS" "LAST SEEN"
    printf "  %s\n" "$(printf '─%.0s' {1..135})"
  else
    printf "\n  %-28s %-15s %-13s %-12s %-22s %s\n" \
      "NAME" "IP" "TYPE" "RULE" "STATUS" "LAST SEEN"
    printf "  %s\n" "$(printf '─%.0s' {1..107})"
  fi
}

function cmd::list::_render_footer() {
  local has_groups="$1"
  if $has_groups; then
    printf "  %s\n" "$(printf '─%.0s' {1..135})"
  else
    printf "  %s\n" "$(printf '─%.0s' {1..107})"
  fi
}

function cmd::list::_render_summary() {
  local group_summary="${1:-}"
  local -n _rule_counts="$2"
  local filter_desc="${3:-}"

  local total=0
  for r in "${!_rule_counts[@]}"; do
    (( total += _rule_counts[$r] )) || true
  done

  local summary=""
  for r in "${!_rule_counts[@]}"; do
    summary+="${_rule_counts[$r]} ${r}, "
  done
  summary="${summary%, }"

  if [[ -n "$group_summary" ]]; then
    printf "\n  Showing %s peers [%s] — %s\n\n" "$total" "$summary" "$group_summary"
  else
    printf "\n  Showing %s peers [%s]\n\n" "$total" "$summary"
  fi
}

# ============================================
# Detail Card
# ============================================

function cmd::list::show_client() {
  local name="${1:-}"
  local conf
  conf="$(ctx::clients)/${name}.conf"

  if [[ ! -f "$conf" ]]; then
    log::error "Client not found: ${name}"
    return 1
  fi

  local ip
  ip=$(grep "^Address" "$conf" | awk '{print $3}' | cut -d'/' -f1)

  local allowed_ips
  allowed_ips=$(grep "^AllowedIPs" "$conf" | cut -d'=' -f2- | xargs)

  local public_key
  public_key=$(keys::public "$name" 2>/dev/null || echo "unknown")

  # Meta type is authoritative; IP reverse lookup is fallback for pre-migration peers
  local type
  type=$(peers::get_meta "$name" "type" 2>/dev/null)
  [[ -z "$type" ]] && type=$(peers::get_type_from_ip "$ip")

  local endpoint="—"
  local ep
  ep=$(monitor::endpoint_for_key "$public_key")
  [[ -z "$ep" ]] && ep=$(monitor::last_endpoint "$name")
  [[ -n "$ep" ]] && endpoint="$ep"

  local is_blocked="false"
  peers::is_blocked "$name" && is_blocked="true"

  local is_restricted="false"
  peers::is_restricted "$name" && is_restricted="true"

  local handshake_ts
  handshake_ts=$(monitor::get_handshake_ts "$public_key")

  local last_ts
  last_ts=$(monitor::last_attempt "$name")

  local status
  status=$(peers::format_status_verbose "$name" "$public_key" \
    "$is_blocked" "$is_restricted" "$handshake_ts" "$last_ts")

  local last_seen
  last_seen=$(peers::format_last_seen "$name" "$public_key" \
    "$is_blocked" "$last_ts" "" "$handshake_ts")

  local blocks=""
  if block::has_file "$name" && block::is_blocked "$name"; then
    if [[ "$(block::is_blocked_direct "$name")" == "true" ]]; then
      blocks="all traffic blocked"
    fi
    local rule_lines
    rule_lines=$(block::format_rules "$name")
    [[ -n "$rule_lines" ]] && blocks+="$rule_lines"
  fi

  ui::section "Client: ${name}"
  ui::row "IP"         "$ip"
  ui::row "Type"       "$(peers::display_type "$type")"
  ui::row "Status"     "$(echo -e "$status")"
  ui::row "Endpoint"   "$endpoint"
  ui::row "Last seen"  "$last_seen"
  ui::row "AllowedIPs" "$allowed_ips"
  ui::row "Public key" "$public_key"

  if [[ -z "$blocks" ]]; then
    ui::row "Blocks" "none"
  elif [[ "$blocks" == *"all traffic blocked"* ]]; then
    ui::row "Blocks" "$(echo -e "\033[1;31mAll\033[0m")"
  else
    printf "  %-20s\n" "Blocks:"
    echo -e "$blocks"
  fi
  printf "\n"
}

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

function cmd::list::run() {
  local filter_type="" filter_rule="" filter_group=""
  local online_only=false offline_only=false
  local restricted_only=false blocked_only=false allowed_only=false
  local detailed=false single_name=""

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --type)       filter_type="$2";     shift 2 ;;
      --rule)       filter_rule="$2";     shift 2 ;;
      --group)      filter_group="$2";    shift 2 ;;
      --online)     online_only=true;     shift   ;;
      --offline)    offline_only=true;    shift   ;;
      --restricted) restricted_only=true; shift   ;;
      --blocked)    blocked_only=true;    shift   ;;
      --allowed)    allowed_only=true;    shift   ;;
      --detailed)   detailed=true;        shift   ;;
      --name)       single_name="$2";     shift 2 ;;
      --help)       cmd::list::help;      return  ;;
      *)
        log::error "Unknown flag: $1"
        cmd::list::help
        return 1
        ;;
    esac
  done

  if [[ -n "$single_name" ]]; then
    cmd::list::show_client "$single_name"
    return 0
  fi

  local dir
  dir="$(ctx::clients)"
  local confs=("${dir}"/*.conf)
  if [[ ! -f "${confs[0]}" ]]; then
    log::wg_warning "No clients configured"
    return 0
  fi

  cmd::list::_precompute_all

  if $detailed; then
    log::section "WireGuard Clients"
    cmd::list::_iter_confs "$filter_type" cmd::list::_show_client_safe
    return 0
  fi

  local filter_desc=""
  cmd::list::_build_filter_desc

  declare -A rule_counts=() group_counts=()
  _list_header_printed=false

  cmd::list::_iter_confs "$filter_type" cmd::list::_render_row

  if [[ "$_list_header_printed" == "true" ]]; then
    cmd::list::_render_footer $has_groups
    local group_summary=""
    cmd::list::_build_group_summary
    cmd::list::_render_summary "$group_summary" rule_counts "$filter_desc"
  else
    log::wg_warning "No results found${filter_desc:+ for: ${filter_desc}}"
  fi
}

# ============================================
# Iteration
# ============================================

function cmd::list::_iter_confs() {
  local filter_type="$1" callback="$2"
  local dir
  dir="$(ctx::clients)"

  for conf in "${dir}"/*.conf; do
    [[ -f "$conf" ]] || continue
    local client_name
    client_name=$(basename "$conf" .conf)
    local ip="${p_ips[$client_name]:-}"
    [[ -z "$ip" ]] && ip=$(grep "^Address" "$conf" | awk '{print $3}' | cut -d'/' -f1)

    # p_types is authoritative — set during precompute from meta + IP fallback
    local type="${p_types[$client_name]:-unknown}"
    [[ -n "$filter_type" && "$type" != "$filter_type" ]] && continue

    "$callback" "$client_name" "$ip" "$type"
  done
}

# ============================================
# Row rendering
# ============================================

function cmd::list::_render_row() {
  local client_name="$1" ip="$2" type="$3"

  local pubkey="${p_pubkeys[$client_name]:-}"
  local handshake_ts="${wg_handshakes[$pubkey]:-0}"
  local is_blocked="${p_blocked[$client_name]:-false}"
  local is_restricted="${p_restricted[$client_name]:-false}"
  local last_ts="${p_last_ts[$client_name]:-}"

  if $online_only;     then peers::is_online  "$client_name" "$handshake_ts" "$last_ts" || return 0; fi
  if $offline_only;    then peers::is_offline "$client_name" "$handshake_ts" "$last_ts" || return 0; fi
  if $restricted_only  && [[ "$is_restricted" != "true" ]]; then return 0; fi
  if $blocked_only     && [[ "$is_blocked"    != "true" ]]; then return 0; fi
  if $allowed_only && { [[ "$is_blocked" == "true" ]] || \
                        [[ "$is_restricted" == "true" ]]; }; then return 0; fi

  if [[ -n "$filter_group" ]]; then
    local all_groups="${peer_group_map[$client_name]:-}"
    [[ "$all_groups" != *"$filter_group"* ]] && return 0
  fi

  local status last_seen display_type rule group_display
  status=$(peers::format_status_verbose "$client_name" "$pubkey" \
    "$is_blocked" "$is_restricted" "$handshake_ts" "$last_ts")
  last_seen=$(peers::format_last_seen "$client_name" "$pubkey" \
    "$is_blocked" "$last_ts" "" "$handshake_ts")
  display_type=$(peers::display_type "$type")
  rule="${p_rules[$client_name]:-—}"

  if [[ -n "$filter_rule" && "$rule" != "$filter_rule" ]]; then return 0; fi

  if [[ "${_list_header_printed:-false}" == "false" ]]; then
    log::section "WireGuard Clients"
    cmd::list::_render_header $has_groups
    _list_header_printed=true
  fi

  rule_counts["$rule"]=$(( ${rule_counts[$rule]:-0} + 1 )) || true

  local padded_status
  padded_status=$(ui::pad_status "$status" 25)

  if $has_groups; then
    local main_group="${p_main_groups[$client_name]:-}"
    if [[ -n "$main_group" ]]; then
      group_display="$main_group"
    else
      group_display="${peer_group_map[$client_name]:-—}"
    fi

    if [[ -n "${peer_group_map[$client_name]:-}" ]]; then
      group_counts["$group_display"]=$(( ${group_counts[$group_display]:-0} + 1 )) || true
    fi

    local rule_col_width=12 group_col_width=12
    [[ "$rule"          == "—" ]] && rule_col_width=14
    [[ "$group_display" == "—" ]] && group_col_width=14
    printf "  %-28s %-15s %-13s %-${rule_col_width}s %-${group_col_width}s %s %s\n" \
      "$client_name" "$ip" "$display_type" "$rule" \
      "$group_display" "$padded_status" "$last_seen"
  else
    local rule_col_width=12
    [[ "$rule" == "—" ]] && rule_col_width=14
    printf "  %-28s %-15s %-13s %-${rule_col_width}s %s %s\n" \
      "$client_name" "$ip" "$display_type" "$rule" \
      "$padded_status" "$last_seen"
  fi
}

# ============================================
# Precompute
# ============================================

function cmd::list::_precompute_all() {
  # Peer data — field 4 is 'type' from peer_data_v2
  declare -gA p_ips=() p_rules=() p_types=() p_last_ts=() p_last_evt=() p_main_groups=()
  while IFS="|" read -r name ip rule type last_ts last_evt main_group; do
    [[ -z "$name" ]] && continue
    p_ips["$name"]="$ip"
    p_rules["$name"]="${rule:-—}"
    p_types["$name"]="${type:-}"
    p_last_ts["$name"]="$last_ts"
    p_last_evt["$name"]="$last_evt"
    p_main_groups["$name"]="${main_group:-}"
  done < <(json::peer_data "$(ctx::clients)" "$(ctx::meta)" "$(ctx::events_log)")

  # Fill type from IP for peers missing meta type (pre-migration peers)
  for name in "${!p_ips[@]}"; do
    [[ -n "${p_types[$name]:-}" ]] && continue
    p_types["$name"]=$(peers::get_type_from_ip "${p_ips[$name]}")
  done

  # WireGuard handshakes + endpoints
  declare -gA wg_handshakes=() wg_endpoints=()
  while IFS=$'\t' read -r pubkey ts; do
    [[ -n "$pubkey" ]] && wg_handshakes["$pubkey"]="$ts"
  done < <(wg show "$(config::interface)" latest-handshakes 2>/dev/null)
  while IFS=$'\t' read -r pubkey endpoint; do
    [[ -n "$pubkey" ]] && wg_endpoints["$pubkey"]="$endpoint"
  done < <(wg show "$(config::interface)" endpoints 2>/dev/null)

  # Block/restricted status
  declare -gA p_blocked=() p_restricted=()
  cmd::list::_precompute_block_status p_blocked p_restricted

  # Public keys
  declare -gA p_pubkeys=()
  local dir
  dir="$(ctx::clients)"
  for kf in "${dir}"/*_public.key; do
    [[ -f "$kf" ]] || continue
    local kname
    kname=$(basename "$kf" _public.key)
    p_pubkeys["$kname"]=$(cat "$kf" 2>/dev/null || echo "")
  done

  # Groups + main group
  has_groups=false
  declare -gA peer_group_map=()
  local groups_dir
  groups_dir="$(ctx::groups)"
  local group_files=("${groups_dir}"/*.group)
  if [[ -f "${group_files[0]}" ]]; then
    has_groups=true
    while IFS=":" read -r peer_name group_name; do
      [[ -n "$peer_name" ]] && peer_group_map["$peer_name"]="$group_name"
    done < <(json::peer_group_map "$groups_dir")
  fi

  # Transfer/activity data — keyed by pubkey
  declare -gA p_rx=() p_tx=() p_activity=()
  while IFS="|" read -r pubkey rx tx level; do
    [[ -z "$pubkey" ]] && continue
    p_rx["$pubkey"]="$rx"
    p_tx["$pubkey"]="$tx"
    p_activity["$pubkey"]="$level"
  done < <(json::peer_transfer "$(config::interface)")
}

function cmd::list::_precompute_block_status() {
  local -n _blocked="$1"
  local -n _restricted="$2"

  local wg_peers
  wg_peers=$(wg show "$(config::interface)" peers 2>/dev/null)

  while IFS= read -r name; do
    if block::has_specific_rules "$name" 2>/dev/null; then
      _restricted["$name"]=true
    else
      _restricted["$name"]=false
    fi

    local pubkey
    pubkey=$(keys::public "$name" 2>/dev/null || echo "")
    if [[ -n "$pubkey" ]] && ! echo "$wg_peers" | grep -qF "$pubkey"; then
      _blocked["$name"]=true
    else
      _blocked["$name"]=false
    fi
  done < <(peers::all)
}

# ============================================
# Filter helpers
# ============================================

function cmd::list::_build_filter_desc() {
  filter_desc=""
  [[ -n "$filter_type"  ]] && filter_desc+="type=${filter_type} "
  [[ -n "$filter_rule"  ]] && filter_desc+="rule=${filter_rule} "
  [[ -n "$filter_group" ]] && filter_desc+="group=${filter_group} "
  $online_only   && filter_desc+="online "
  $offline_only  && filter_desc+="offline "
  $blocked_only  && filter_desc+="blocked "
  filter_desc="${filter_desc% }"
}

function cmd::list::_build_group_summary() {
  group_summary=""
  if $has_groups; then
    declare -A _gs=()
    for peer in "${!peer_group_map[@]}"; do
      local g="${peer_group_map[$peer]}"
      _gs["$g"]=$(( ${_gs["$g"]:-0} + 1 )) || true
    done
    for g in "${!_gs[@]}"; do
      group_summary+="${_gs[$g]} in ${g}, "
    done
    group_summary="${group_summary%, }"
  fi
}

function cmd::list::_show_client_safe() {
  local name="$1"
  cmd::list::show_client "$name" || true
}