#!/usr/bin/env bash

# ============================================
# Client Config
# ============================================

function peers::create_client_config() {
  local name="$1"
  local type="$2"
  local ip="$3"
  local allowed_ips="${4:-$(config::allowed_ips_for "split")}"

  local conf
  conf="$(ctx::clients)/${name}.conf"

  if [[ -f "$conf" ]]; then
    log::wg_warning "Client config already exists: ${name}"
    return 1
  fi

  local private_key
  private_key=$(keys::private "$name")

  local server_public_key
  server_public_key=$(config::server_public_key)

  cat > "$conf" <<EOF
[Interface]
PrivateKey = ${private_key}
Address = ${ip}/32
DNS = $(config::dns_string)

[Peer]
PublicKey = ${server_public_key}
Endpoint = $(config::endpoint)
AllowedIPs = ${allowed_ips}
PersistentKeepalive = 25
EOF

  chmod 600 "$conf"
  log::debug "Created client config: ${name} (${ip})"
}

function peers::remove_client_config() {
  local name="$1"
  local conf
  conf="$(ctx::clients)/${name}.conf"

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

  rm -f "$conf"
  log::debug "Removed client config: ${name}"
}

# ============================================
# Server Config (wg0.conf) Peer Management
# ============================================

function peers::cleanup_config() {
  json::cleanup_config "$(config::config_file)"
}


function peers::add_to_server() {
  local name="${1:?name required}"
  local public_key="${2:?public_key required}"  
  local ip="${3:?ip required}"

  local config
  config=$(config::config_file)

  cat >> "$config" <<EOF

[Peer]
# ${name}
PublicKey = ${public_key}
AllowedIPs = ${ip}/32
EOF

  log::debug "Added peer to server config: ${name}"
}

function peers::remove_block() {
  local name="${1:?name required}"
  json::remove_peer_block "$(config::config_file)" "$name"
}

function peers::remove_from_server() {
  local name="${1:?name required}"
  peers::remove_block "$name"
  peers::cleanup_config
  log::debug "Removed peer from server config: ${name}"
}

# ============================================
# Listing
# ============================================

function peers::list() {
  local dir
  dir="$(ctx::clients)"

  if [[ -z "$(ls -A "$dir"/*.conf 2>/dev/null)" ]]; then
    log::wg_list "No clients configured"
    return 0
  fi

  for conf in "${dir}"/*.conf; do
    local client_name
    client_name=$(basename "$conf" .conf)

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

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

    local type
    type=$(peers::get_meta "$client_name" "type" 2>/dev/null)
    [[ -z "$type" ]] && type=$(peers::get_type_from_ip "$ip")

    printf "  %-30s %-15s %-10s %s\n" \
      "$client_name" "$ip" "$type" "$public_key"
  done
}

function peers::list_by_type() {
  local filter_type="$1"
  local dir
  dir="$(ctx::clients)"

  for conf in "${dir}"/*.conf; do
    local client_name
    client_name=$(basename "$conf" .conf)

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

    local type
    type=$(peers::get_meta "$client_name" "type" 2>/dev/null)
    [[ -z "$type" ]] && type=$(peers::get_type_from_ip "$ip")

    [[ "$type" == "$filter_type" ]] && \
      printf "  %-30s %-15s\n" "$client_name" "$ip"
  done
}

function peers::exists_in_server() {
  local name="$1"
  grep -q "^# ${name}$" "$(config::config_file)"
}

function peers::is_blocked() {
  local name="${1:-}"
  block::is_blocked "$name"
}

function peers::is_restricted() {
  local name="${1:-}"
  block::has_specific_rules "$name" 2>/dev/null
}

# ============================================
# Default Rule
# ============================================

function peers::get_type() {
  local name="$1"
  local ip
  ip=$(peers::get_ip "$name")
  [[ -z "$ip" ]] && echo "unknown" && return 0
  peers::get_type_from_ip "$ip"
}

function peers::default_rule() {
  echo "user"
}

function peers::effective_rule() {
  local name="$1"
  local rule
  rule=$(peers::get_meta "$name" "rule")
  echo "${rule:---}"
}

# ============================================
# Query
# ============================================

function peers::all() {
  local dir
  dir="$(ctx::clients)"
  for conf in "${dir}"/*.conf; do
    [[ -f "$conf" ]] || continue
    basename "$conf" .conf
  done
}

function peers::with_rule() {
  local rule="$1"
  while IFS= read -r name; do
    local effective
    effective=$(peers::effective_rule "$name")
    [[ "$effective" == "$rule" ]] && echo "$name"
  done < <(peers::all)
}

function peers::get_ip() {
  local name="$1"
  grep "^Address" "$(ctx::clients)/${name}.conf" 2>/dev/null \
    | awk '{print $3}' | cut -d'/' -f1 || true
}

function peers::find_by_ip() {
  local target_ip="$1"
  while IFS= read -r name; do
    local ip
    ip=$(peers::get_ip "$name")
    [[ "$ip" == "$target_ip" ]] && echo "$name" && return 0
  done < <(peers::all)
}

function peers::is_connected() {
  local handshake_ts="${1:-0}"
  local now diff threshold
  now=$(date +%s)
  threshold=$(config::handshake_time_sec)
  [[ "$handshake_ts" == "0" || -z "$handshake_ts" ]] && return 1
  diff=$(( now - handshake_ts ))
  (( diff < threshold ))
}

function peers::is_attempting() {
  local last_ts="${1:-}"
  [[ -z "$last_ts" ]] && return 1
  local now attempt_ts diff threshold
  now=$(date +%s)
  threshold=$(config::handshake_time_sec)
  attempt_ts=$(json::iso_to_ts "$last_ts")
  [[ -z "$attempt_ts" || "$attempt_ts" == "0" ]] && return 1
  diff=$(( now - attempt_ts ))
  (( diff < threshold ))
}

function peers::is_online() {
  local name="${1:-}" handshake_ts="${2:-0}" last_ts="${3:-}"
  local is_blocked
  peers::is_blocked "$name" && is_blocked="true" || is_blocked="false"
  if [[ "$is_blocked" == "true" ]]; then
    peers::is_attempting "$last_ts"
  else
    peers::is_connected "$handshake_ts"
  fi
}

function peers::is_offline() {
  local name="${1:-}" handshake_ts="${2:-0}" last_ts="${3:-}"
  peers::is_online "$name" "$handshake_ts" "$last_ts" && return 1 || return 0
}

function peers::get_main_group() {
  local name="${1:?}"
  peers::get_meta "$name" "main_group"
}

function peers::set_main_group() {
  local name="${1:?}" group="${2:?}"
  peers::set_meta "$name" "main_group" "$group"
}

function peers::get_display_subnet() {
  local peer_name="${1:-}" peer_type="${2:-}"
  local subnet
  subnet=$(peers::get_meta "$peer_name" "subnet" 2>/dev/null)
  if [[ -z "$subnet" ]]; then
    subnet=$(subnet::type_from_ip "$(peers::get_ip "$peer_name")" 2>/dev/null)
    [[ -n "$peer_type" ]] && subnet="$peer_type"
  fi
  [[ -z "$subnet" ]] && subnet="-"
  echo "$subnet"
}

# ============================================
# Name + Type Parsing
# ============================================

function peers::resolve_name() {
  local name="$1"
  local type="${2:-}"

  if [[ -n "$type" ]]; then
    if ! subnet::exists "$type"; then
      log::error "Invalid device type: ${type}"
      return 1
    fi
    echo "${type}-${name}"
  else
    echo "$name"
  fi
}

function peers::require_exists() {
  local name="$1"
  if [[ ! -f "$(ctx::clients)/${name}.conf" ]]; then
    log::error "Client not found: ${name}"
    return 1
  fi
}

function peers::resolve_and_require() {
  local name="$1"
  local type="${2:-}"
  
  local resolved
  resolved=$(peers::resolve_name "$name" "$type") || return 1
  peers::require_exists "$resolved" || return 1
  echo "$resolved"
}

function peers::rename_meta() {
  local name="${1:-}" new_name="${2:-}"
  local old_meta new_meta
  old_meta=$(peers::meta_path "$name")
  new_meta=$(peers::meta_path "$new_name")
  [[ -f "$old_meta" ]] && mv "$old_meta" "$new_meta"
  return 0
}

# ============================================
# Cleanup
# ============================================

function peers::purge() {
  local name="${1:-}" client_ip="${2:-}" was_blocked="${3:-false}"
 
  [[ -n "$client_ip" ]] && fw::flush_peer "$client_ip"
  peers::remove_from_server "$name"   || return 1
  peers::remove_client_config "$name" || return 1
  keys::remove "$name"                || return 1
  group::remove_peer_from_all "$name" || return 1
 
  if [[ -n "$client_ip" ]] && $was_blocked; then
    fw::unblock_all "$client_ip"
  fi
 
  block::remove_file "$name"          2>/dev/null || true
  peers::remove_meta "$name"          2>/dev/null || true
  peers::reload                       || return 1
}
 

# ============================================
# Display / Formatting
# ============================================

function peers::format_last_seen() {
  local name="${1:-}" pubkey="${2:-}" is_blocked="${3:-false}"
  local last_ts="${4:-}" last_evt="${5:-}" handshake_ts="${6:-0}"

  local data
  data=$(peers::last_seen_data "$is_blocked" "$last_ts" "$handshake_ts")

  local ts type
  IFS="|" read -r ts type <<< "$data"

  case "$type" in
    none)      echo "—" ;;
    dropped)   echo "$(fmt::datetime_iso "$ts") (dropped)" ;;
    handshake) echo "$(fmt::datetime "$ts") (handshake)" ;;
  esac
}

function peers::format_status() {
  local name="${1:-}" public_key="${2:-}" is_blocked="${3:-false}"
  local is_restricted="${4:-false}" handshake_ts="${5:-0}" last_ts="${6:-}"

  local state
  state=$(peers::connection_state "$is_blocked" "$is_restricted" \
    "$handshake_ts" "$last_ts")

  local conn_str modifier color
  IFS="|" read -r conn_str modifier color <<< "$state"

  # Color based on state — modifier overrides base connection color
  if [[ "$is_blocked" == "true" ]]; then
    color="\033[1;31m"   # red — blocked
  elif [[ "$is_restricted" == "true" ]]; then
    color="\033[1;33m"   # yellow — restricted
  elif [[ "$conn_str" == "online" ]]; then
    color="\033[1;32m"   # green — online
  else
    color="\033[0;37m"   # gray — offline
  fi

  local conn_str_padded
  conn_str_padded=$(printf "%-7s" "$conn_str")
  echo -e "${color}${conn_str_padded}\033[0m"
}

# Inspect — verbose, color + descriptive text
function peers::format_status_verbose() {
  local name="${1:-}" public_key="${2:-}" is_blocked="${3:-false}"
  local is_restricted="${4:-false}" handshake_ts="${5:-0}" last_ts="${6:-}"

  local conn_str
  local state
  state=$(peers::connection_state "$is_blocked" "$is_restricted" \
    "$handshake_ts" "$last_ts")
  IFS="|" read -r conn_str _ _ <<< "$state"

  local color suffix=""
  if [[ "$is_blocked" == "true" ]]; then
    color="\033[1;31m"
    suffix=" (blocked)"
  elif [[ "$is_restricted" == "true" ]]; then
    color="\033[1;33m"
    suffix=" (restricted)"
  elif [[ "$conn_str" == "online" ]]; then
    color="\033[1;32m"
  else
    color="\033[0;37m"
  fi

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

function peers::display_type() {
  local type="${1:-}" _subtype="${2:-}"
  echo "${type:-unknown}"
}

# ============================================
# Connection data
# ============================================

# Data functions — return raw values
function peers::connection_state() {
  # Returns: connected|modifier|color_code
  local is_blocked="${1:-false}" is_restricted="${2:-false}"
  local handshake_ts="${3:-0}" last_ts="${4:-}"
  local threshold
  threshold=$(config::handshake_time_sec)
  local now
  now=$(date +%s)
  local connected=false modifier="" color

  if [[ "$is_blocked" == "true" ]]; then
    local attempt_ts diff
    attempt_ts=$(json::iso_to_ts "${last_ts:-0}")
    diff=$(( now - attempt_ts ))
    (( diff < threshold )) && connected=true
    modifier="blocked"
    color="\033[1;31m"
  elif [[ "$is_restricted" == "true" ]]; then
    local diff=$(( now - handshake_ts ))
    (( diff < threshold )) && connected=true
    modifier="restricted"
    color="\033[1;33m"
  else
    local diff=$(( now - handshake_ts ))
    (( diff < threshold )) && connected=true
    $connected && color="\033[1;32m" || color="\033[0;37m"
  fi

  $connected && echo "online|${modifier}|${color}" || echo "offline|${modifier}|${color}"
}

function peers::last_seen_data() {
  # Returns: timestamp|type (dropped|handshake|none)
  local is_blocked="${1:-false}" last_ts="${2:-}" handshake_ts="${3:-0}"

  if [[ "$is_blocked" == "true" ]]; then
    if [[ -n "$last_ts" && "$last_ts" != "0" && "$last_ts" != "null" ]]; then
      echo "${last_ts}|dropped"
    else
      echo "|none"
    fi
  else
    if [[ -z "$handshake_ts" || "$handshake_ts" == "0" ]]; then
      echo "|none"
    else
      echo "${handshake_ts}|handshake"
    fi
  fi
}

function peers::get_type_from_ip() {
  local ip="${1:-}"
  [[ -z "$ip" ]] && echo "unknown" && return 0
  subnet::type_from_ip "$ip"
}


# ============================================
# Activity
# ============================================

# Returns: level|rx|tx
function peers::activity_total() {
  local pubkey="${1:-}"
  json::peer_transfer "$(config::interface)" | grep "^${pubkey}" | head -1 | cut -d'|' -f2-
}

# Returns: level|rx_rate|tx_rate  
function peers::activity_current() {
  local pubkey="${1:-}"
  json::peer_transfer_delta "$(config::interface)" \
    "$(ctx::daemon)/transfer_cache.json" \
    | grep "^${pubkey}" | head -1 | cut -d'|' -f2-
}

function peers::format_activity_total() {
  local pubkey="${1:-}"
  local data
  data=$(peers::activity_total "$pubkey")
  [[ -z "$data" ]] && echo "—" && return 0
  local level rx tx rx_hr tx_hr
  IFS="|" read -r rx tx level <<< "$data"
  rx_hr=$(numfmt --to=iec "${rx:-0}" 2>/dev/null || echo "0B")
  tx_hr=$(numfmt --to=iec "${tx:-0}" 2>/dev/null || echo "0B")
  echo "${level:-none} (↓${rx_hr} ↑${tx_hr})"
}

function peers::format_activity_current() {
  local pubkey="${1:-}"
  local data
  data=$(peers::activity_current "$pubkey")
  [[ -z "$data" ]] && echo "—" && return 0
  local level rx_rate tx_rate rx_hr tx_hr
  IFS="|" read -r rx_rate tx_rate level <<< "$data"
  [[ "$level" == "unknown" ]] && echo "sampling..." && return 0
  local rx_hr tx_hr
  rx_hr=$(numfmt --to=iec "${rx_rate:-0}" 2>/dev/null || echo "${rx_rate:-0}")
  tx_hr=$(numfmt --to=iec "${tx_rate:-0}" 2>/dev/null || echo "${tx_rate:-0}")
  echo "${level} (↓${rx_hr}B/s ↑${tx_hr}B/s)"
}

# ============================================
# dentity
# ============================================

function peers::get_identity() {
  local peer_name="${1:-}"
  local id_dir
  id_dir="$(ctx::identities)"
  for id_file in "${id_dir}"/*.identity; do
    [[ -f "$id_file" ]] || continue
    if json::get "$id_file" "peers" 2>/dev/null | grep -qx "$peer_name"; then
      basename "$id_file" .identity
      return 0
    fi
  done
  return 1
}

# ============================================
# Helpers - Meta File
# ============================================

function peers::meta_path() {
  local name="$1"
  echo "$(ctx::meta)/${name}.meta"
}

function peers::get_meta() {
  local name="$1" key="$2"
  json::get "$(peers::meta_path "$name")" "$key"
}

function peers::set_meta() {
  local name="$1" key="$2" value="$3"
  json::set "$(peers::meta_path "$name")" "$key" "$value"
}

function peers::remove_meta() {
  local name="$1"
  rm -f "$(peers::meta_path "$name")"
}

# ============================================
# Live Reload
# ============================================

function peers::reload() {
  wg syncconf "$(config::interface)" <(wg-quick strip "$(config::interface)")
  log::debug "WireGuard config reloaded"
}
