#!/usr/bin/env bash

FW_EVENTS_LOG="$(ctx::fw_events_log)"
WG_EVENTS_LOG="$(ctx::events_log)"

function cmd::logs::on_load() {
  flag::register --name
  flag::register --type
  flag::register --since
  flag::register --limit
  flag::register --fw
  flag::register --wg
  flag::register --follow
  flag::register --merged
  flag::register --all
  flag::register --before
  flag::register --force
  flag::register --days
  flag::register --raw
  flag::register --detailed
}

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

Show or manage WireGuard and firewall activity logs.

Subcommands:
  show (default)    Show activity logs
  remove, rm        Remove log entries
  rotate            Remove entries older than N days

Options for show:
  --name <name>     Filter by client name
  --type <type>     Filter by device type
  --limit <n>       Max results per source (default: 50)
  --fw              Show only firewall drops
  --wg              Show only WireGuard events
  --merged          Show all events chronologically interleaved
  --follow, -f      Follow logs in real time (alias: wgctl watch)
  --raw             Show raw IPs without service annotation

Options for remove:
  --name <name>     Remove entries for specific peer
  --all             Remove all log entries
  --fw              Remove only firewall events
  --wg              Remove only WireGuard events
  --before <days>   Remove entries older than N days
  --force           Skip confirmation

Options for rotate:
  --days <n>        Days to keep (default: 7)
  --force           Skip confirmation

Examples:
  wgctl logs
  wgctl logs --name phone-nuno
  wgctl logs --fw --limit 100
  wgctl logs --merged
  wgctl logs --follow
  wgctl logs remove --name phone-nuno
  wgctl logs rotate --days 30
EOF
}

function cmd::logs::run() {
  local subcmd="${1:-show}"
  if [[ "$subcmd" == --* ]]; then
    subcmd="show"
  else
    shift || true
  fi

  case "$subcmd" in
    show)           cmd::logs::show   "$@" ;;
    remove|rm|del)  cmd::logs::remove "$@" ;;
    rotate)         cmd::logs::rotate "$@" ;;
    help)           cmd::logs::help        ;;
    *)
      log::error "Unknown subcommand: '${subcmd}'"
      cmd::logs::help
      return 1
      ;;
  esac
}

function cmd::logs::show() {
  local name="" type="" limit=50
  local fw_only=false wg_only=false follow=false merged=false raw=false detailed=false
  

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name)      name="$2";       shift 2 ;;
      --type)      type="$2";       shift 2 ;;
      --limit)     limit="$2";      shift 2 ;;
      --fw)        fw_only=true;    shift   ;;
      --wg)        wg_only=true;    shift   ;;
      --merged)    merged=true;     shift   ;;
      --follow|-f) follow=true;     shift   ;;
      --raw)       raw=true;        shift   ;;
      --detailed)  detailed=true    shift   ;;
      --help)      cmd::logs::help; return  ;;
      *)
        log::error "Unknown flag: $1"
        return 1
        ;;
    esac
  done

  local collapse=1
  $detailed && collapse=0


  if [[ -n "$name" && -n "$type" ]]; then
    name=$(peers::resolve_and_require "$name" "$type") || return 1
  fi

  local filter_ip=""
  if [[ -n "$name" ]]; then
    filter_ip=$(peers::get_ip "$name")
    [[ -z "$filter_ip" ]] && log::error "Could not find IP for: $name" && return 1
  fi

  if $follow; then
    cmd::logs::follow "$filter_ip" "$name" "$type" "$fw_only" "$wg_only"
    return
  fi

  local net_file=""
  $raw || net_file="$(ctx::net)"

  log::section "WireGuard Activity Log"
  printf "\n"

  if $merged; then
    cmd::logs::show_merged "$filter_ip" "$name" "$type" "$limit" "$net_file"
    return
  fi

  $wg_only || cmd::logs::show_fw_events "$filter_ip" "$name" "$type" "$limit" "$net_file" "$collapse"
  $fw_only || cmd::logs::show_wg_events "$filter_ip" "$name" "$type" "$limit" "$collapse"
}

function cmd::logs::show_fw_events() {
  local filter_ip="${1:-}" filter_name="${2:-}" filter_type="${3:-}" \
        limit="${4:-50}" net_file="${5:-}" collapse="${6:-1}"

  [[ ! -f "$FW_EVENTS_LOG" ]] && return 0

  local data
  data=$(json::fw_events "$FW_EVENTS_LOG" "$filter_ip" "$filter_type" \
    "$(ctx::clients)" "${net_file:-}" "$limit" "$collapse" 2>/dev/null)

  [[ -z "$data" ]] && return 0

  # Measure column widths
  local w_client=16 w_dest=20
  while IFS='|' read -r ts client dest_ip dest_port proto svc count; do
    [[ -z "$ts" ]] && continue
    (( ${#client} > w_client )) && w_client=${#client}
    local dest_display
    local host_name
    host_name=$(hosts::resolve_ip "$dest_ip")
    if [[ -n "$host_name" ]]; then
      dest_display="$host_name"
    elif [[ -n "$svc" ]]; then
      [[ -n "$dest_port" ]] && dest_display="${svc}/${proto}" || dest_display="${svc} (${proto})"
    else
      [[ -n "$dest_port" ]] && dest_display="${dest_ip}:${dest_port}/${proto}" || dest_display="${dest_ip} (${proto})"
    fi
    (( ${#dest_display} > w_dest )) && w_dest=${#dest_display}
  done <<< "$data"
  (( w_client += 2 ))
  (( w_dest   += 2 ))

  ui::logs::fw_section_header
  while IFS='|' read -r ts client dest_ip dest_port proto svc count; do
    [[ -z "$ts" ]] && continue
    ui::logs::fw_row "$ts" "$client" "$dest_ip" "$dest_port" \
      "$proto" "$svc" "$count" "$w_client" "$w_dest"
  done <<< "$data"
  printf "\n"
}

function cmd::logs::show_wg_events() {
  local filter_ip="${1:-}" filter_name="${2:-}" filter_type="${3:-}" \
        limit="${4:-50}" collapse="${5:-1}"
 
  [[ ! -f "$WG_EVENTS_LOG" ]] && return 0
 
  local data
  data=$(json::wg_events "$WG_EVENTS_LOG" "$filter_name" "$filter_type" "$limit" "$collapse" 2>/dev/null)
 
  [[ -z "$data" ]] && return 0
 
  # Resolve endpoints and measure column widths
  local w_client=16 w_endpoint=16
  local resolved_data=""
  while IFS='|' read -r ts client endpoint event count; do
    [[ -z "$ts" ]] && continue
    local endpoint_display
    endpoint_display=$(resolve::ip "$endpoint")
    [[ -z "$endpoint_display" ]] && endpoint_display="$endpoint"
    resolved_data+="${ts}|${client}|${endpoint_display}|${event}|${count}"$'\n'
    (( ${#client}           > w_client   )) && w_client=${#client}
    (( ${#endpoint_display} > w_endpoint )) && w_endpoint=${#endpoint_display}
  done <<< "$data"
  (( w_client   += 2 ))
  (( w_endpoint += 2 ))
 
  ui::logs::wg_section_header
  while IFS='|' read -r ts client endpoint event count; do
    [[ -z "$ts" ]] && continue
    ui::logs::wg_row "$ts" "$client" "$endpoint" "$event" \
      "$count" "$w_client" "$w_endpoint"
  done <<< "$resolved_data"
  printf "\n"
}
 

function cmd::logs::show_merged() {
  local filter_ip="${1:-}" filter_name="${2:-}" filter_type="${3:-}" \
        limit="${4:-50}" net_file="${5:-}"

  local fw_data wg_data
  fw_data=$(json::fw_events "$FW_EVENTS_LOG" "$filter_ip" "$filter_type" \
    "$(ctx::clients)" "${net_file:-}" "$limit" 2>/dev/null)
  wg_data=$(json::wg_events "$WG_EVENTS_LOG" "$filter_name" "$filter_type" \
    "$limit" 2>/dev/null)

  # Measure widths across both sources
  local w_client=16 w_dest=20
  while IFS='|' read -r ts client rest; do
    [[ -z "$ts" ]] && continue
    (( ${#client} > w_client )) && w_client=${#client}
  done < <(echo "$fw_data"; echo "$wg_data")
  (( w_client += 2 ))

  # Tag and merge: prefix fw lines with "fw|", wg lines with "wg|"
  local merged_data
  merged_data=$(
    while IFS='|' read -r ts client dest_ip dest_port proto svc count; do
      [[ -z "$ts" ]] && continue
      echo "fw|${ts}|${client}|${dest_ip}|${dest_port}|${proto}|${svc}|${count}"
    done <<< "$fw_data"
    while IFS='|' read -r ts client endpoint event count; do
      [[ -z "$ts" ]] && continue
      echo "wg|${ts}|${client}|${endpoint}|${event}|${count}"
    done <<< "$wg_data"
  )

  # Sort by timestamp field 2
  while IFS='|' read -r source ts rest; do
    [[ -z "$source" ]] && continue
    case "$source" in
      fw)
        IFS='|' read -r client dest_ip dest_port proto svc count <<< "$rest"
        local dest_display
        if [[ -n "$svc" ]]; then
          [[ -n "$dest_port" ]] && dest_display="${svc}/${proto}" || dest_display="${svc} (${proto})"
        else
          [[ -n "$dest_port" ]] && dest_display="${dest_ip}:${dest_port}/${proto}" || dest_display="${dest_ip} (${proto})"
        fi
        (( ${#dest_display} > w_dest )) && w_dest=${#dest_display}
        ;;
    esac
  done <<< "$merged_data"
  (( w_dest += 2 ))

  while IFS='|' read -r source ts rest; do
    [[ -z "$source" ]] && continue
    case "$source" in
      fw)
        IFS='|' read -r client dest_ip dest_port proto svc count <<< "$rest"
        ui::watch::fw_row "$ts" "$client" \
          "$(ui::logs::build_dest "$dest_ip" "$dest_port" "$proto" "$svc")" \
          "$w_client" "$w_dest"
        ;;
      wg)
        IFS='|' read -r client endpoint event count <<< "$rest"
        ui::watch::wg_row "$ts" "$client" "$endpoint" "$event" \
          "$w_client" "$w_dest"
        ;;
    esac
  done < <(echo "$merged_data" | sort -t'|' -k2,2)

  printf "\n"
}

function cmd::logs::follow() {
  local filter_ip="${1:-}" filter_name="${2:-}" filter_type="${3:-}"
  local fw_only="${4:-false}" wg_only="${5:-false}"

  log::section "WireGuard Live Log (Ctrl+C to stop)"
  printf "\n"

  # Delegate to watch command
  local watch_args=()
  [[ -n "$filter_name" ]] && watch_args+=(--name "$filter_name")
  [[ -n "$filter_type" ]] && watch_args+=(--type "$filter_type")
  $fw_only && watch_args+=(--restricted)
  $wg_only && watch_args+=(--blocked)

  cmd::watch::run "${watch_args[@]}"
}

function cmd::logs::remove() {
  local name="" type="" before="" force=false
  local fw_only=false wg_only=false all=false

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --name)   name="$2";       shift 2 ;;
      --type)   type="$2";       shift 2 ;;
      --before) before="$2";     shift 2 ;;
      --fw)     fw_only=true;    shift   ;;
      --wg)     wg_only=true;    shift   ;;
      --all)    all=true;        shift   ;;
      --force)  force=true;      shift   ;;
      --help)   cmd::logs::help; return  ;;
      *)
        log::error "Unknown flag: $1"
        return 1
        ;;
    esac
  done

  if ! $all && [[ -z "$name" && -z "$before" ]]; then
    log::error "Specify --name, --before, or --all"
    cmd::logs::help
    return 1
  fi

  local filter_ip=""
  if [[ -n "$name" ]]; then
    name=$(peers::resolve_and_require "$name" "$type") || return 1
    filter_ip=$(peers::get_ip "$name")
  fi

  local desc=""
  $all    && desc="all entries"
  [[ -n "$name"   ]] && desc="entries for '${name}'"
  [[ -n "$before" ]] && desc="${desc:+$desc, }entries older than ${before} days"
  $fw_only && desc="${desc} (fw only)"
  $wg_only && desc="${desc} (wg only)"

  if ! $force; then
    read -r -p "Remove ${desc}? [y/N] " confirm
    case "$confirm" in
      [yY]*) ;;
      *) log::info "Aborted"; return 0 ;;
    esac
  fi

  local result
  result=$(json::remove_events_filtered \
    "$WG_EVENTS_LOG" "$FW_EVENTS_LOG" \
    "${name:-}" "${filter_ip:-}" \
    "$fw_only" "$wg_only" \
    "${before:-}")

  local removed_wg removed_fw
  IFS="|" read -r removed_wg removed_fw <<< "$result"
  local total=$(( removed_wg + removed_fw ))

  if [[ "$total" -eq 0 ]]; then
    log::wg_warning "No log entries found matching the criteria"
    return 0
  fi

  log::wg_success "Removed ${total} log entries (wg: ${removed_wg}, fw: ${removed_fw})"
}

function cmd::logs::rotate() {
  local days=7 force=false

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --days)  days="$2";    shift 2 ;;
      --force) force=true;   shift   ;;
      --help)  cmd::logs::help; return ;;
      *) log::error "Unknown flag: $1"; return 1 ;;
    esac
  done

  $force || {
    read -r -p "Remove log entries older than ${days} days? [y/N] " confirm
    case "$confirm" in
      [yY]*) ;;
      *) log::info "Aborted"; return 0 ;;
    esac
  }

  local result
  result=$(json::remove_events_filtered \
    "$WG_EVENTS_LOG" "$FW_EVENTS_LOG" \
    "" "" "false" "false" "$days")

  local removed_wg removed_fw
  IFS="|" read -r removed_wg removed_fw <<< "$result"
  local total=$(( removed_wg + removed_fw ))

  if [[ "$total" -eq 0 ]]; then
    log::wg_warning "No log entries older than ${days} days"
    return 0
  fi

  log::wg_success "Rotated ${total} entries older than ${days} days (wg: ${removed_wg}, fw: ${removed_fw})"
}