#!/usr/bin/env bash

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

function cmd::watch::on_load() {
  flag::register --type
  flag::register --name
  flag::register --peers
  flag::register --blocked
  flag::register --restricted
  flag::register --allowed
}

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

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

Live monitor of WireGuard activity. Shows:
  - Handshakes from connected peers (green)
  - Connection attempts from blocked peers (red, SOURCE: wg)
  - Firewall drops from rule-restricted peers (red, SOURCE: fw)

Options:
  --name <name>     Filter by client name
  --type <type>     Filter by device type
  --peers <list>    Comma-separated peer names (used internally by group watch)
  --blocked         Show only blocked peer attempts
  --allowed         Show only handshakes (allowed peers)
  --restricted      Show only firewall drop events

Examples:
  wgctl watch
  wgctl watch --blocked
  wgctl watch --allowed
  wgctl watch --type phone
  wgctl watch --name phone-nuno
  wgctl watch --name phone-nuno --type phone
EOF
}

# ============================================
# Helpers
# ============================================

function cmd::watch::format_event() {
  local ts="${1:-}" source="${2:-}" client="${3:-}"
  local dest="${4:-}" event="${5:-}" status="${6:-}"

  local event_color
  case "$event" in
    attempt|drop) event_color="\033[1;31m" ;;
    handshake)    event_color="\033[1;32m" ;;
    *)            event_color="\033[0;37m" ;;
  esac

  local status_color=""
  case "$status" in
    blocked) status_color="\033[1;31m" ;;
    allowed) status_color="\033[1;32m" ;;
  esac

  printf "  %-20s %-8s %-22s %-28s ${event_color}%-14s\033[0m ${status_color}%s\033[0m\n" \
    "$ts" "$source" "$client" "${dest:-—}" "$event" "$status"
}

function cmd::watch::header() {
  log::section "wgctl — Live Monitor (Ctrl+C to stop)"
  printf "\n  %-20s %-8s %-22s %-28s %-14s %s\n" \
    "TIME" "SOURCE" "CLIENT" "DESTINATION/ENDPOINT" "EVENT" "STATUS"
  printf "  %s\n\n" "$(printf '─%.0s' {1..105})"
}

function cmd::watch::_peer_in_filter() {
  local peer="$1"
  shift
  local peer_set=("$@")
  [[ ${#peer_set[@]} -eq 0 ]] && return 0  # no filter = all pass
  for p in "${peer_set[@]}"; do
    [[ "$p" == "$peer" ]] && return 0
  done
  return 1
}

# ============================================
# Handshake Poller
# ============================================

function cmd::watch::poll_handshakes() {
  local filter_name="${1:-}" filter_type="${2:-}"
  local allowed_only="${3:-false}"
  local filter_peers="${4:-}"

  local peer_set=()
  [[ -n "$filter_peers" ]] && IFS=',' read -ra peer_set <<< "$filter_peers"

  while IFS= read -r line; do
    local public_key ts
    public_key=$(echo "$line" | awk '{print $1}')
    ts=$(echo "$line" | awk '{print $2}')

    [[ -z "$ts" || "$ts" == "0" ]] && continue

    # Find client by public key
    local client_name=""
    for conf in "$(ctx::clients)"/*.conf; do
      [[ -f "$conf" ]] || continue
      local name
      name=$(basename "$conf" .conf)
      local key
      key=$(keys::public "$name" 2>/dev/null || echo "")
      if [[ "$key" == "$public_key" ]]; then
        client_name="$name"
        break
      fi
    done

    [[ -z "$client_name" ]] && continue

    # Apply filters
    [[ -n "$filter_name" && "$client_name" != "$filter_name" ]] && continue
    cmd::watch::_peer_in_filter "$client_name" "${peer_set[@]}" || continue

    if [[ -n "$filter_type" ]]; then
      local ip
      ip=$(grep "^Address" "$(ctx::clients)/${client_name}.conf" \
        | awk '{print $3}' | cut -d'/' -f1)
      local subnet
      subnet=$(config::subnet_for "$filter_type")
      string::starts_with "$ip" "$subnet" || continue
    fi

    # Only emit if handshake is new
    local safe_key
    safe_key=$(echo "$public_key" | md5sum | cut -d' ' -f1)
    local prev_ts_file="/tmp/wgctl_hs_${safe_key}"
    local prev_ts="0"
    [[ -f "$prev_ts_file" ]] && prev_ts=$(cat "$prev_ts_file")

    if [[ "$ts" != "$prev_ts" ]]; then
      echo "$ts" > "$prev_ts_file"

      local formatted_ts
      formatted_ts=$(fmt::datetime "$ts")

      local endpoint
      endpoint=$(monitor::endpoint_for_key "$public_key")

      cmd::watch::format_event \
        "$formatted_ts" "wg" "$client_name" "${endpoint:-—}" "handshake" "allowed"
    fi

  done < <(wg show "$(config::interface)" latest-handshakes 2>/dev/null)
}

# ============================================
# Event Tailer
# ============================================

function cmd::watch::tail_events() {
  local filter_name="${1:-}" filter_type="${2:-}"
  local blocked_only="${3:-false}" restricted_only="${4:-false}"
  local allowed_only="${5:-false}" filter_peers="${6:-}"

  declare -A _WATCH_LAST_ATTEMPT=()
  declare -A _WATCH_LAST_FW=()

  local peer_set=()
  [[ -n "$filter_peers" ]] && IFS=',' read -ra peer_set <<< "$filter_peers"

  declare -A _WATCH_LAST_ATTEMPT=()

  # Build ip->name map for fw events
  declare -A ip_to_name=()
  local conf_file
  while IFS= read -r conf_file; do
    local name
    name=$(basename "$conf_file" .conf)
    local ip
    ip=$(grep "^Address" "$conf_file" 2>/dev/null | awk '{print $3}' | cut -d'/' -f1)
    [[ -n "$ip" && -n "$name" ]] && ip_to_name["$ip"]="$name"
  done < <(find "$(ctx::clients)" -name "*.conf" 2>/dev/null)

  # Source tracker via temp file (persists across subshell iterations)
  local source_file
  source_file=$(mktemp)
  echo "wg" > "$source_file"

  # Cleanup temp file on exit
  trap "rm -f '$source_file'" EXIT

  tail -f "$(ctx::events_log)" "$(ctx::fw_events_log)" 2>/dev/null \
  | while IFS= read -r line; do
    [[ -z "$line" ]] && continue

    # Handle tail -f file headers
    if [[ "$line" == "==> "* ]]; then
      if [[ "$line" == *"fw_events"* ]]; then
        echo "fw" > "$source_file"
      else
        echo "wg" > "$source_file"
      fi
      continue
    fi

    local source
    source=$(cat "$source_file")

    if [[ "$source" == "wg" ]]; then
      $allowed_only && continue  # wg events are attempts/blocked

      local event_data
      event_data=$(json::parse_event "$line")
      [[ -z "$event_data" ]] && continue

      local ts client endpoint event
      IFS="|" read -r ts client endpoint event <<< "$event_data"

      [[ -n "$filter_name" && "$client" != "$filter_name" ]] && continue
      cmd::watch::_peer_in_filter "$client" "${peer_set[@]}" || continue

      if [[ -n "$filter_type" ]]; then
        local conf
        conf="$(ctx::clients)/${client}.conf"
        [[ -f "$conf" ]] || continue
        local ip
        ip=$(grep "^Address" "$conf" 2>/dev/null | awk '{print $3}' | cut -d'/' -f1)
        local subnet
        subnet=$(config::subnet_for "$filter_type")
        string::starts_with "$ip" "$subnet" || continue
      fi

      $restricted_only && { cmd::list::is_restricted "$client" || continue; }

      # Dedup
      local now
      now=$(date +%s)
      local safe_client="${client//[-.]/_}"
      local last="${_WATCH_LAST_ATTEMPT[$safe_client]:-0}"
      (( now - last < 30 )) && continue
      _WATCH_LAST_ATTEMPT[$safe_client]="$now"

      local formatted_ts
      formatted_ts=$(fmt::datetime_iso "$ts")

      cmd::watch::format_event \
        "$formatted_ts" "wg" "$client" "${endpoint:-—}" "$event" "blocked"

    else
      # FW event
      $allowed_only && continue
      $blocked_only && continue  # fw drops aren't "blocked peers" per se

      local fw_data
      fw_data=$(json::parse_fw_event "$line")
      [[ -z "$fw_data" ]] && continue

      local ts src_ip dst_ip dst_port proto
      IFS="|" read -r ts src_ip dst_ip dst_port proto <<< "$fw_data"

      [[ -z "$src_ip" ]] && continue

      local fw_key="${src_ip}:${dst_ip}:${dst_port}:${proto}"
      local now
      now=$(date +%s)
      local last_fw="${_WATCH_LAST_FW[$fw_key]:-0}"

      local window=30
      [[ "$proto" == "17" || "$proto" == "udp" ]]  && window=10
      [[ "$proto" == "1"  || "$proto" == "icmp" ]] && window=5

      local diff=$(( now - last_fw ))
      (( diff < window )) && continue
      _WATCH_LAST_FW["$fw_key"]="$now"

      local client="${ip_to_name[$src_ip]:-$src_ip}"

      [[ -n "$filter_name" && "$client" != "$filter_name" ]] && continue
      cmd::watch::_peer_in_filter "$client" "${peer_set[@]}" || continue

      if [[ -n "$filter_type" ]]; then
        local peer_client="${ip_to_name[$src_ip]:-}"
        [[ -z "$peer_client" ]] && continue
        local conf
        conf="$(ctx::clients)/${peer_client}.conf"
        [[ -f "$conf" ]] || continue
        local ip
        ip=$(grep "^Address" "$conf" 2>/dev/null | awk '{print $3}' | cut -d'/' -f1)
        local subnet
        subnet=$(config::subnet_for "$filter_type")
        string::starts_with "$ip" "$subnet" || continue
      fi

      local dst_str="${dst_ip:-—}"
      [[ -n "$dst_port" ]] && dst_str="${dst_ip}:${dst_port}/${proto}"

      local formatted_ts
      formatted_ts=$(fmt::datetime_iso "$ts")

      cmd::watch::format_event \
        "$formatted_ts" "fw" "$client" "$dst_str" "drop" "blocked"
    fi
  done

  rm -f "$source_file"
}

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

function cmd::watch::run() {
  local filter_name="" filter_type="" filter_peers=""
  local blocked_only=false allowed_only=false restricted_only=false

  rm -f /tmp/wgctl_hs_* /tmp/wgctl_attempt_* 2>/dev/null || true

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name)       filter_name="$2";     shift 2 ;;
      --type)       filter_type="$2";     shift 2 ;;
      --peers)      filter_peers="$2";    shift 2 ;;
      --blocked)    blocked_only=true;    shift   ;;
      --allowed)    allowed_only=true;    shift   ;;
      --restricted) restricted_only=true; shift   ;;
      --help)       cmd::watch::help;     return  ;;
      *)
        log::error "Unknown flag: $1"
        cmd::watch::help
        return 1
        ;;
    esac
  done

  cmd::watch::header

  if ! $blocked_only && ! $restricted_only; then
    (
      while true; do
        cmd::watch::poll_handshakes \
          "$filter_name" "$filter_type" "$allowed_only" "$filter_peers"
        sleep 5
      done
    ) &
    local poller_pid=$!
  fi

  cmd::watch::tail_events \
    "$filter_name" "$filter_type" \
    "$blocked_only" "$restricted_only" \
    "$allowed_only" "$filter_peers" &
  local tailer_pid=$!

  trap "kill $tailer_pid ${poller_pid:-} 2>/dev/null; \
    rm -f /tmp/wgctl_hs_* /tmp/wgctl_attempt_*; echo ''; exit 0" INT TERM

  wait
}