#!/usr/bin/env bash

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

function cmd::list::on_load() {
  flag::register --type
  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
  --online          Show only connected clients
  --offline         Show only disconnected clients
  --allowed         Show only fully allowed clients
  --restricted      Show only restricted clients
  --blocked         Show only blocked clients
  --detailed        Show full detail cards for all clients
  --name <name>     Show detail card for a single client

Examples:
  wgctl list
  wgctl ls --type phone
  wgctl list --online
  wgctl list --blocked
  wgctl list --allowed
  wgctl list --restricted
  wgctl list --detailed
  wgctl list --name phone-nuno
EOF
}

# ============================================
# Status Helpers
# ============================================

function cmd::list::last_handshake_ts() {
  local public_key="$1"
  wg show "$(config::interface)" latest-handshakes 2>/dev/null \
    | grep "^${public_key}" \
    | awk '{print $2}'
}

function cmd::list::last_dropped_ts() {
  local client_ip="$1"
  journalctl -k --grep "wgctl-dropped: " 2>/dev/null \
    | grep "SRC=${client_ip}" \
    | tail -1 \
    | awk '{print $1, $2, $3}'
}

function cmd::list::is_connected() {
  local public_key="$1"
  local ts
  ts=$(cmd::list::last_handshake_ts "$public_key")
  [[ -z "$ts" || "$ts" == "0" ]] && return 1
  local now diff
  now=$(date +%s)
  diff=$(( now - ts ))
  (( diff < 180 ))
}

function cmd::list::is_attempting() {
  local name="$1"
  local ts
  ts=$(monitor::last_attempt "$name")
  [[ -z "$ts" ]] && return 1
  
  local now attempt_ts diff
  now=$(date +%s)
  attempt_ts=$(python3 -c "
from datetime import datetime, timezone
dt = datetime.fromisoformat('${ts}')
if dt.tzinfo is None:
    dt = dt.replace(tzinfo=timezone.utc)
print(int(dt.timestamp()))
" 2>/dev/null || echo 0)
  
  diff=$(( now - attempt_ts ))
  (( diff < 180 ))
}

function cmd::list::is_blocked() {
  local name="$1"
  peers::is_blocked "$name"
}

function cmd::list::is_restricted() {
  local name="$1"
  [[ -f "$(ctx::block::path "${name}.block")" ]]
}

function cmd::list::format_last_seen() {
  local name="$1"
  local public_key="$2"
  local ip="$3"

  if cmd::list::is_blocked "$name"; then
    local ts
    ts=$(monitor::last_attempt "$name")
    if [[ -n "$ts" ]]; then
      # Format ISO timestamp
      local formatted
      formatted=$(python3 -c "
from datetime import datetime, timezone
dt = datetime.fromisoformat('${ts}')
print(dt.strftime('%Y-%m-%d %H:%M'))
" 2>/dev/null || echo "$ts")
      echo "${formatted} (dropped)"
    else
      echo "—"
    fi
  else
    local ts
    ts=$(cmd::list::last_handshake_ts "$public_key")
    if [[ -z "$ts" || "$ts" == "0" ]]; then
      echo "—"
    else
      local formatted
      formatted=$(date -d "@${ts}" "+%Y-%m-%d %H:%M" 2>/dev/null || echo "$ts")
      echo "${formatted} (handshake)"
    fi
  fi
}

function cmd::list::format_status() {
  local name="$1"
  local public_key="$2"
  local ip="$3"          # new

  local connected=false
  local blocked=false
  local restricted=false

  cmd::list::is_blocked "$name"     && blocked=true
  cmd::list::is_restricted "$name"  && restricted=true

  if $blocked; then
    cmd::list::is_attempting "$name" && connected=true
    modifier=" (blocked)"
  elif $restricted; then
    cmd::list::is_connected "$public_key" && connected=true
    modifier=" (restricted)"
  else
    cmd::list::is_connected "$public_key" && connected=true
    modifier=""
  fi

  local conn_str
  $connected && conn_str="online" || conn_str="offline"

  local status="${conn_str}${modifier}"

  local color
  if $blocked; then
    color="\033[1;31m"
  elif $restricted; then
    color="\033[1;33m"
  elif $connected; then
    color="\033[1;32m"
  else
    color="\033[0;37m"
  fi

  echo -e "${color}${status}\033[0m"
}

# ============================================
# Display Type
# ============================================

function cmd::list::display_type() {
  local name="$1"
  local type="$2"
  
  # log::debug "$(config::is_guest_type "$type")"
  if config::is_guest_type "$type"; then
    local subtype
    subtype=$(peers::get_meta "$name" "subtype")
    # log::debug "$subtype"

    if [[ -n "$subtype" ]]; then
      echo "guest/${subtype}"
    else
      echo "guest"
    fi
  else
    echo "$type"
  fi
}

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

function cmd::list::show_client() {
  local name="$1"
  local dir
  dir="$(ctx::clients)"
  local conf="${dir}/${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" | awk '{print $3}')

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

  # Get endpoint
  local endpoint="—"
  if cmd::list::is_blocked "$name"; then
    local ep
    ep=$(monitor::last_endpoint "$name")
    [[ -n "$ep" ]] && endpoint="$ep"
  else
    local ep
    ep=$(monitor::endpoint_for_key "$public_key")
    [[ -n "$ep" ]] && endpoint="$ep"
  fi

  # Determine type
  local type="unknown"
  for t in $(config::device_types); do
    local subnet
    subnet=$(config::subnet_for "$t")
    if string::starts_with "$ip" "$subnet."; then
      type="$t"
      break
    fi
  done

  local status
  status=$(cmd::list::format_status "$name" "$public_key" "$ip")

  local last_seen
  last_seen=$(cmd::list::format_last_seen "$name" "$public_key" "$ip")

  # Block rules
  local block_file
  block_file="$(ctx::block::path "${name}.block")"
  local blocks=""

if [[ -f "$block_file" ]] && [[ -s "$block_file" ]]; then
    while IFS=" " read -r client_ip target port proto; do
      if [[ -z "$target" ]]; then
        blocks+="      all traffic blocked\n"
      else
        local rule="      ${target}"
        [[ -n "$port" ]] && rule+=":${port}/${proto}"
        blocks+="${rule}\n"
      fi
    done < "$block_file"
  fi

  local sep
  sep="$(printf '─%.0s' {1..50})"

  echo ""
  echo "  ${sep}"
  printf "  \033[1;34m%-20s\033[0m %s\n" "Client:" "$name"
  echo "  ${sep}"
  printf "  %-20s %s\n"  "IP:"             "$ip"
  printf "  %-20s %s\n"  "Type:"           "$type"
  printf "  %-20s %b\n"  "Status:"         "$status"
  printf "  %-20s %s\n"  "Endpoint:"       "$endpoint"
  printf "  %-20s %s\n"  "Last seen:"      "$last_seen"
  printf "  %-20s %s\n"  "Allowed IPs:"    "$allowed_ips"
  printf "  %-20s %s\n"  "Public key:"     "$public_key"

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

  echo "  ${sep}"
  echo ""
}

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

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

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --type)       filter_type="$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

  # Single client detail card
  if [[ -n "$single_name" ]]; then
    cmd::list::show_client "$single_name"
    return
  fi

  local dir
  dir="$(ctx::clients)"
  local confs=("${dir}"/*.conf)

  if [[ ! -f "${confs[0]}" ]]; then
    log::wg_list "No clients configured"
    return 0
  fi

  # START - GROUP SECTION
  # Check if any groups exist
  local has_groups=false
  local groups_dir
  groups_dir="$(ctx::groups)"
  local group_files=("${groups_dir}"/*.group)
  [[ -f "${group_files[0]}" ]] && has_groups=true

  # Precompute peer->group map
  declare -A peer_group_map
  if $has_groups; then
    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
  # END - GROUP SECTION

  # Detailed mode — cards only, no table
  if $detailed; then
    log::section "WireGuard Clients"
    for conf in "${dir}"/*.conf; do
      [[ -f "$conf" ]] || continue
      local client_name
      client_name=$(basename "$conf" .conf)

      # Apply type filter
      if [[ -n "$filter_type" ]]; then
        local ip
        ip=$(grep "^Address" "$conf" | awk '{print $3}' | cut -d'/' -f1)
        local type="unknown"
        for t in $(config::device_types); do
          local subnet
          subnet=$(config::subnet_for "$t")
          if string::starts_with "$ip" "$subnet."; then
            type="$t"
            break
          fi
        done
        [[ "$type" != "$filter_type" ]] && continue
      fi

      cmd::list::show_client "$client_name"
    done
    return
  fi

  # Normal table view
  log::section "WireGuard Clients"

  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

  for conf in "${dir}"/*.conf; do
    [[ -f "$conf" ]] || continue

    local client_name
    client_name=$(basename "$conf" .conf)

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

    # Determine type
    local type="unknown"
    for t in $(config::device_types); do
      local subnet
      subnet=$(config::subnet_for "$t")
      if string::starts_with "$ip" "$subnet."; then
        type="$t"
        break
      fi
    done

    # Apply type filter
    if [[ -n "$filter_type" && "$type" != "$filter_type" ]]; then
      continue
    fi

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

    # Apply filters
    if $online_only && ! cmd::list::is_connected "$public_key"; then
      continue
    fi

    if $offline_only && cmd::list::is_connected "$public_key"; then
      continue
    fi

    if $restricted_only && ! cmd::list::is_restricted "$client_name"; then
      continue
    fi

    if $blocked_only && ! cmd::list::is_blocked "$client_name"; then
      continue
    fi

    if $allowed_only && { cmd::list::is_blocked "$client_name" || cmd::list::is_restricted "$client_name"; }; then
      continue
    fi

    local status
    status=$(cmd::list::format_status "$client_name" "$public_key" "$ip")

    local last_seen
    last_seen=$(cmd::list::format_last_seen "$client_name" "$public_key" "$ip")

    local display_type
    display_type=$(cmd::list::display_type "$client_name" "$type")
    # log::debug "display_type called with name=$client_name type=$type"

  local rule
  rule=$(peers::effective_rule "$client_name")
  rule="${rule:-—}"

  local padded_status
  padded_status=$(cmd::list::pad_status "$status" 25)
  local group_display="—"
    if $has_groups; then
      group_display="${peer_group_map[$client_name]:-—}"
    fi

    local rule_col_width=12
    [[ "$rule" == "—" ]] && rule_col_width=14

    local group_col_width=12
    [[ "$group_display" == "—" ]] && group_col_width=14

    if $has_groups; then
    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
      printf "  %-28s %-15s %-13s %-12s %s %s\n" \
        "$client_name" "$ip" "$display_type" "$rule" \
        "$padded_status" "$last_seen"
    fi
  done

  if $has_groups; then
    printf "  %s\n" "$(printf '─%.0s' {1..135})"
  else
    printf "  %s\n" "$(printf '─%.0s' {1..107})"
  fi

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

  cmd::list::_render_summary "$group_summary"

  printf "\n"
}

function cmd::list::_render_summary() {
  local group_summary="${1:-}"
  # Summary line
  local total online_count
  total=$(peers::all | wc -l)
  
  # Count by rule
  declare -A rule_summary
  while IFS= read -r peer_name; do
    local r
    r=$(peers::effective_rule "$peer_name")
    rule_summary["$r"]=$(( ${rule_summary["$r"]:-0} + 1 ))
  done < <(peers::all)
  
  local summary=""
  for r in "${!rule_summary[@]}"; do
    summary+="${rule_summary[$r]} ${r}, "
  done
  summary="${summary%, }"  # remove trailing comma
  
  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
}

# Strip ANSI codes to measure visible length, then pad manually
function cmd::list::pad_status() {
  local status="$1"
  local width="${2:-20}"
  local visible
  visible=$(echo -e "$status" | sed 's/\x1b\[[0-9;]*m//g')
  local pad=$(( width - ${#visible} ))
  printf "%b%${pad}s" "$status" ""
}