#!/usr/bin/env bash

function cmd::inspect::on_load() {
  flag::register --name
  flag::register --type
  flag::register --config
  flag::register --qr

  command::mixin json_output
}

function cmd::inspect::help() {
  cat <<EOF
Usage: wgctl inspect --name <name> [options]
       wgctl inspect <full-name>

Show detailed information for a WireGuard client.

Sections shown:
  Client      — IP, type, rule, status, activity
  Groups      — group memberships
  Rule        — firewall rule with inheritance tree and service annotations
  Peer Blocks — peer-specific restrictions (beyond the assigned rule)
  Firewall    — active iptables rules with ACCEPT/DROP counts

Options:
  --name <name>     Client name (e.g. phone-nuno)
  --type <type>     Device type — combines with --name
  --config          Also show raw WireGuard client config
  --qr              Also show QR code

Examples:
  wgctl inspect --name phone-nuno
  wgctl inspect --name nuno --type phone
  wgctl inspect --name phone-nuno --config
  wgctl inspect --name phone-nuno --qr
  wgctl inspect guest-zephyr
EOF
}

INSPECT_WIDTH=48  # total visible width of section lines
INSPECT_LABEL_WIDTH=20

# ============================================
# Private helpers
# ============================================


function cmd::inspect::_section() {
  local title="${1:-}" extra="${2:-0}"
  local width=$(( INSPECT_WIDTH + extra ))
  local title_len=${#title}
  # Account for "── " (3) + " " (1) before dashes
  local dash_count=$(( width - title_len - 4 ))
  [[ $dash_count -lt 2 ]] && dash_count=2
  local dashes
  dashes=$(printf '─%.0s' $(seq 1 $dash_count))
  printf "\n  \033[0;37m── %s %s\033[0m\n" "$title" "$dashes"
}

function cmd::inspect::_peer_info() {
  local name="${1:-}"

  local ip type rule public_key allowed_ips
  ip=$(peers::get_ip "$name")
  type=$(peers::get_type "$name")
  rule=$(peers::get_meta "$name" "rule")
  public_key=$(keys::public "$name" 2>/dev/null || echo "")
  allowed_ips=$(grep "^AllowedIPs" "$(ctx::clients)/${name}.conf" \
    2>/dev/null | cut -d'=' -f2- | xargs)

  # Status
  local handshake_ts is_blocked last_ts
  handshake_ts=$(monitor::get_handshake_ts "$public_key")
  peers::is_blocked "$name" && is_blocked="true" || is_blocked="false"
  last_ts=$(monitor::last_attempt "$name")

  local is_restricted="false"
  block::has_specific_rules "$name" 2>/dev/null && is_restricted="true"

  local status last_seen endpoint
  status=$(peers::format_status_verbose "$name" "$public_key" \
    "$is_blocked" "$is_restricted" "$handshake_ts" "$last_ts")
  last_seen=$(peers::format_last_seen "$name" "$public_key" \
    "$is_blocked" "$last_ts" "" "$handshake_ts")
  endpoint=$(monitor::get_cached_endpoint "$name")

  local activity_total
  activity_total=$(peers::format_activity_total "$public_key")

  local activity_current
  activity_current=$(peers::format_activity_current "$public_key")

  local rule_file=""
  local rule_extends=""
  if [[ -n "$rule" ]]; then
    rule_file="$(rule::path "$rule" 2>/dev/null)" || true
    if [[ -n "$rule_file" ]]; then
      local ext=()
      mapfile -t ext < <(json::get "$rule_file" "extends" 2>/dev/null || true)
      if [[ ${#ext[@]} -gt 0 && -n "${ext[0]:-}" ]]; then
        rule_extends=" (↳ ${ext[*]})"
      fi
    fi
  fi

  # Rule formatting
  local rule_display="${rule:-—}"
  if [[ -n "$rule_file" && ${#ext[@]} -gt 0 && -n "${ext[0]:-}" ]]; then
    local extends_str
    extends_str=$(printf '%s, ' "${ext[@]}" | sed 's/, $//')
    rule_display="${rule} ↳ (${extends_str})"
  fi

  cmd::inspect::_section "Client"
  printf "\n"
  ui::row "Name"       "$name" "${INSPECT_LABEL_WIDTH}"
  ui::row "IP"         "$ip" "${INSPECT_LABEL_WIDTH}"
  ui::row "Type"       "$(peers::display_type "$type")" "${INSPECT_LABEL_WIDTH}"
  ui::row "Rule"        "$rule_display" "${INSPECT_LABEL_WIDTH}"
  ui::row "Status"     "$(echo -e "$status")" "${INSPECT_LABEL_WIDTH}"
  ui::row "Endpoint"   "${endpoint:-—}" "${INSPECT_LABEL_WIDTH}"
  ui::row "Last seen"  "$last_seen" "${INSPECT_LABEL_WIDTH}"
  ui::row "AllowedIPs" "$allowed_ips" "${INSPECT_LABEL_WIDTH}"
  ui::row "Public key" "${public_key:-—}" "${INSPECT_LABEL_WIDTH}"
  ui::row "Activity (total)"   "$activity_total" "${INSPECT_LABEL_WIDTH}"
  ui::row "Activity (current)"  "$activity_current" "${INSPECT_LABEL_WIDTH}"

  return 0
}

function cmd::inspect::_rule_separator() {
  local line_width=20
  local total=$INSPECT_WIDTH
  local pad=$(( (total - line_width) / 2 ))
  printf "\n%*s\033[2m%s\033[0m\n\n" "$pad" "" "$(printf '─%.0s' $(seq 1 $line_width))"
}

function cmd::inspect::_rule_info() {
  local name="${1:-}"
  local rule
  rule=$(peers::get_meta "$name" "rule")
 
  local identity_name identity_rules strict
  identity_name=$(identity::get_name "$name")
  if [[ -n "$identity_name" ]]; then
    identity_rules=$(identity::rules "$identity_name")
    strict=$(identity::rule_flags "$identity_name" "strict_rule")
  fi
 
  # Skip section entirely if nothing to show
  [[ -z "$rule" && -z "$identity_rules" ]] && return 0
 
  # Build section header
  local header="Rules"
  [[ -n "$rule" ]] && header="${header}: ${rule}"
  [[ -n "$identity_name" && -n "$identity_rules" ]] && \
    header="${header} · identity:${identity_name}"
 
  cmd::inspect::_section "$header"
 
  # Identity block first
  if [[ -n "$identity_name" && -n "$identity_rules" ]]; then
    ui::rule::identity_block "$identity_name" "$strict"
  fi
 
  # Peer rule block — only if set and not suppressed
  if [[ -n "$rule" ]]; then
    rule::exists "$rule" || return 0
 
    if [[ -n "$identity_rules" ]]; then
      # Both identity and peer rules exist — show peer block with same pattern
      printf "\n    \033[0;37m· peer:%s\033[0m\n" "$name"
      ui::rule::_peer_rule_entry "$rule"
    else
      # Only peer rule — render directly without peer: label
      printf "\n"
      if rule::render_extends_tree "$rule"; then
        :
      else
        rule::render_flat "$rule"
      fi
    fi
  elif [[ "$strict" == "true" && -n "$rule" ]]; then
    printf "\n    \033[2mpeer rule '%s' suppressed by strict policy\033[0m\n" "$rule"
  fi
 
  return 0
}

function cmd::inspect::_blocks_info() {
  local name="${1:-}"
  block::has_file "$name" || return 0

  local blocked_direct 
  blocked_direct=$(block::is_blocked_direct "$name")

  local blocked_groups 
  blocked_groups=$(block::get_groups "$name")
  
  local rules_output
  rules_output=$(block::get_rules "$name")

  # Skip if truly empty
  if [[ "$blocked_direct" != "true" ]] && \
     ui::empty "$blocked_groups" && \
     ui::empty "$rules_output"; then
    block::cleanup "$name"  # clean up stale empty file
    return 0
  fi

  # Count rules for header
  local rule_count=0
  while IFS= read -r line; do
    [[ -n "$line" ]] && (( rule_count++ )) || true
  done <<< "$rules_output"

  # Build header like firewall: Blocks (+N)
  local header_counts=""
  [[ "$rule_count" -gt 0 ]] && header_counts=" (${rule_count})"
  [[ "$blocked_direct" == "true" || -n "$blocked_groups" ]] && \
    header_counts="${header_counts} 🚫"

  cmd::inspect::_section "Blocks${header_counts}"
  printf "\n"

  [[ "$blocked_direct" == "true" ]] && \
    printf "    \033[1;31m🚫\033[0m blocked directly\n"
  [[ -n "$blocked_groups" ]] && \
    printf "    \033[1;31m🚫\033[0m blocked by groups: %s\n" "$blocked_groups"

  block::format_rules "$name"
  return 0
}

function cmd::inspect::_group_info() {
  local name="$1"

  local groups=()
  mapfile -t groups < <(json::peer_groups "$(ctx::groups)" "$name")

  ui::empty "${groups[*]}" && return 0

  local count=${#groups[@]}
  cmd::inspect::_section "Groups (${count})"
  printf "\n"

  for g in "${groups[@]}"; do
    [[ -z "$g" ]] && continue
    local peer_count
    local main_marker=""
    peer_count=$(json::count "$(group::path "$g")" "peers")
    [[ "$g" == "$(peers::get_main_group "$name")" ]] && \
      main_marker=" \033[0;33m★\033[0m"
    printf "    \033[0;37m·\033[0m %-20s \033[0;37m%s peers\033[0m%b\n" \
      "$g" "$peer_count" "$main_marker"
  done

  return 0
}

function cmd::inspect::_firewall_info() {
  local name="${1:-}"
  local ip
  ip=$(peers::get_ip "$name")

  local total=0 accepts=0 drops=0
  local rules_output=()
  while IFS= read -r line; do
    [[ -z "$line" ]] && continue
    (( total++ )) || true
    [[ "$line" =~ ACCEPT ]] && (( accepts++ )) || true
    [[ "$line" =~ DROP   ]] && (( drops++   )) || true
    rules_output+=("$line")
  done < <(fw::forward_rules_for_ip "$ip" | grep -v NFLOG)

  ui::empty "${rules_output[*]}" && return 0

  printf "\n  \033[0;37m── Firewall (%s %s) \033[0m%s\n\n" \
      "$(color::green "+${accepts}")" \
      "$(color::red "-${drops}")" \
      "$(printf '\033[0;37m─%.0s' {1..28})"

  fw::list_peer_rules "$ip" false

  return 0
}

function cmd::inspect::_config() {
  local name="$1"
  cmd::inspect::_section "Config"
  printf "\n"
  cat "$(ctx::clients)/${name}.conf"
  printf "\n"

  return 0
}

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

function cmd::inspect::run() {
  local name="" type="" show_config=false show_qr=false

  if [[ $# -gt 0 && "$1" != "--"* ]]; then
    name="$1"
    shift
  fi

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name)   name="$2";             shift 2 ;;
      --type)   type="$2";             shift 2 ;;
      --config) show_config=true;      shift   ;;
      --qr)     show_qr=true;          shift   ;;
      --help)   cmd::inspect::help;    return  ;;
      *)
        log::error "Unknown flag: $1"
        cmd::inspect::help
        return 1
        ;;
    esac
  done

  if [[ -z "$name" ]]; then
    log::error "Missing required flag: --name"
    cmd::inspect::help
    return 1
  fi

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

  if command::json; then
    cmd::inspect::_output_json "$name"
    return 0
  fi

  load_command list

  log::section "Inspect: ${name}"

  cmd::inspect::_peer_info "$name"      
  cmd::inspect::_group_info "$name"     
  cmd::inspect::_rule_info "$name"      
  cmd::inspect::_blocks_info "$name"    
  cmd::inspect::_firewall_info "$name"  

  if $show_config; then
    cmd::inspect::_config "$name"
  fi

  if $show_qr; then
    cmd::inspect::_section "QR Code"
    printf "\n"
    load_command qr
    cmd::qr::run --name "$name"
  fi

  printf "\n"
}

# ============================================
# JSON (API consumption)
# ============================================

function cmd::inspect::_output_json() {
  local name="${1:-}"
 
  local ip type rule allowed_ips public_key is_blocked status
  ip=$(peers::get_ip "$name")
  type=$(peers::get_type "$name")
  rule=$(peers::get_meta "$name" "rule")
  allowed_ips=$(grep "^AllowedIPs" "$(ctx::clients)/${name}.conf" 2>/dev/null | \
    awk '{print $3}' | tr -d ',')
  public_key=$(keys::public "$name" 2>/dev/null || echo "")
  peers::is_blocked "$name" && is_blocked="true" || is_blocked="false"
 
  # Handshake status
  local handshake_ts=0
  handshake_ts=$(wg show "$(config::interface)" latest-handshakes 2>/dev/null | \
    grep "$public_key" | awk '{print $2}') || handshake_ts=0
 
  local last_ts
  last_ts=$(peers::get_meta "$name" "last_ts" 2>/dev/null || echo "")
 
  local conn_state
  conn_state=$(peers::connection_state "$is_blocked" "false" \
    "${handshake_ts:-0}" "${last_ts:-}" | cut -d'|' -f1)
 
  # Groups
  local groups_json="[]"
  local -a group_list=()
  while IFS= read -r g; do
    [[ -n "$g" ]] && group_list+=("\"$g\"")
  done < <(json::peer_groups "$(ctx::groups)" "$name" 2>/dev/null)
  [[ ${#group_list[@]} -gt 0 ]] && \
    groups_json="[$(printf '%s,' "${group_list[@]}" | sed 's/,$//')]"
 
  # Identity
  local identity
  identity=$(peers::get_identity "$name" 2>/dev/null || echo "")
 
  # Rule extends
  local rule_extends="[]"
  if [[ -n "$rule" ]]; then
    local rule_file
    rule_file=$(json::find_rule_file "$(ctx::rules)" "$rule" 2>/dev/null)
    if [[ -n "$rule_file" ]]; then
      local -a extends=()
      while IFS= read -r ext; do
        [[ -n "$ext" ]] && extends+=("\"$ext\"")
      done < <(json::get "$rule_file" "extends" 2>/dev/null)
      [[ ${#extends[@]} -gt 0 ]] && \
        rule_extends="[$(printf '%s,' "${extends[@]}" | sed 's/,$//')]"
    fi
  fi
 
  local data
  data=$(printf '{"name":"%s","ip":"%s","type":"%s","rule":"%s","rule_extends":%s,"allowed_ips":"%s","public_key":"%s","is_blocked":%s,"status":"%s","identity":"%s","groups":%s}' \
    "$name" "$ip" "$type" \
    "${rule:-}" "$rule_extends" \
    "${allowed_ips:-}" "$public_key" \
    "$is_blocked" "$conn_state" \
    "${identity:-}" "$groups_json")
 
  printf '%s' "$data" | json::envelope "inspect" "1"
}