#!/usr/bin/env bash
# test/integration.sh — integration test sections
# Tests run against the live wgctl binary.
# Sourced by test.command.sh — do not execute directly.

WGCTL_BINARY="$(command -v wgctl)"

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

function cmd::test::_strip_ansi() {
  sed 's/\x1b\[[0-9;]*m//g'
}

function cmd::test::run_cmd() {
  local desc="$1" expected="${2:-}"
  shift 2

  local tmp exit_code
  tmp=$(mktemp)

  set +e
  timeout 30 "$WGCTL_BINARY" "$@" > "$tmp" 2>&1
  exit_code=$?
  set -e

  # Reset terminal color in case command output left ANSI state dirty
  printf "\033[0m" >&2

  if [[ $exit_code -eq 124 ]]; then
    test::warn "${desc} (timed out after 30s)"
    rm -f "$tmp"
    return 1
  fi

  local clean
  clean=$(cmd::test::_strip_ansi < "$tmp")

  if [[ $exit_code -ne 0 ]]; then
    local msg="${desc}"
    [[ -n "$expected" ]] && msg="${desc} (expected '${expected}', command failed)"
    test::fail "$msg"
    if [[ "${WGCTL_TEST_VERBOSE:-false}" == "true" ]]; then
      printf "    Output: %s\n" "$(echo "$clean" | head -3 | tr '\n' ' ')"
    fi
    rm -f "$tmp"
    return 1
  fi

  if [[ -n "$expected" ]] && ! echo "$clean" | grep -qF "$expected"; then
    local actual
    actual=$(echo "$clean" | head -3 | tr '\n' ' ' | sed 's/  */ /g' | cut -c1-100)
    test::fail "${desc} (expected '${expected}', got: '${actual}')"
    rm -f "$tmp"
    return 1
  fi

  test::pass "$desc"
  rm -f "$tmp"
}

function cmd::test::run_cmd_any() {
  local desc="$1" expected="${2:-}"
  shift 2

  local tmp
  tmp=$(mktemp)

  set +e
  timeout 30 "$WGCTL_BINARY" "$@" > "$tmp" 2>&1
  set -e

  printf "\033[0m" >&2

  local clean
  clean=$(cmd::test::_strip_ansi < "$tmp")

  if [[ -n "$expected" ]] && ! echo "$clean" | grep -qF "$expected"; then
    local actual
    actual=$(echo "$clean" | head -3 | tr '\n' ' ' | sed 's/  */ /g' | cut -c1-100)
    test::fail "${desc} (expected '${expected}', got: '${actual}')"
    rm -f "$tmp"
    return 1
  fi

  test::pass "$desc"
  rm -f "$tmp"
}

function cmd::test::run_cmd_fails() {
  local desc="$1"
  shift

  local tmp exit_code
  tmp=$(mktemp)

  set +e
  timeout 10 "$WGCTL_BINARY" "$@" > "$tmp" 2>&1
  exit_code=$?
  set -e

  printf "\033[0m" >&2
  rm -f "$tmp"

  if [[ $exit_code -eq 124 ]]; then
    test::warn "${desc} (timed out)"
    return 1
  fi

  if [[ $exit_code -eq 0 ]]; then
    test::fail "${desc} (expected failure but succeeded)"
    return 1
  fi

  test::pass "$desc"
}

# ============================================
# Sections
# ============================================

function cmd::test::run_all_integration_sections() {
  cmd::test::section_list
  cmd::test::section_inspect
  cmd::test::section_config
  cmd::test::section_rules
  cmd::test::section_groups
  cmd::test::section_audit
  cmd::test::section_logs
  cmd::test::section_fw
  cmd::test::section_net
  cmd::test::section_subnet
  cmd::test::section_identity
}

function cmd::test::section_list() {
  test::section "List"
  cmd::test::run_cmd "list"                   "rule:"      list
  cmd::test::run_cmd "list --online"          ""           list --online
  cmd::test::run_cmd "list --offline"         ""           list --offline
  cmd::test::run_cmd "list --blocked"         ""           list --blocked
  cmd::test::run_cmd "list --type phone"      "phone"      list --type phone
  cmd::test::run_cmd "list --detailed"        "rule:"      list --detailed
  cmd::test::run_cmd "list --name phone-nuno" "phone-nuno" list --name phone-nuno
}

function cmd::test::section_inspect() {
  test::section "Inspect"
  cmd::test::run_cmd "inspect --name phone-nuno"          "IP:"        inspect --name phone-nuno
  cmd::test::run_cmd "inspect --name nuno --type phone"   "IP:"        inspect --name nuno --type phone
  cmd::test::run_cmd "inspect --name phone-nuno --config" "PrivateKey" inspect --name phone-nuno --config
  cmd::test::run_cmd_fails "inspect nonexistent"                       inspect --name nonexistent-peer
}

function cmd::test::section_config() {
  test::section "Config & QR"
  cmd::test::run_cmd "config --name phone-nuno"        "PrivateKey" config --name phone-nuno
  cmd::test::run_cmd "config --name nuno --type phone" "PrivateKey" config --name nuno --type phone
  cmd::test::run_cmd "qr --name phone-nuno"            ""           qr --name phone-nuno
}

function cmd::test::section_rules() {
  test::section "Rules"
  cmd::test::run_cmd "rule list"                   "user"        rule list
  cmd::test::run_cmd "rule show --name guest"      "Description" rule show --name guest
  cmd::test::run_cmd "rule show --name user"       "Description" rule show --name user
  cmd::test::run_cmd "rule show --name admin"      "Description" rule show --name admin
  cmd::test::run_cmd_fails "rule show nonexistent"               rule show --name nonexistent
}

function cmd::test::section_groups() {
  test::section "Groups"
  cmd::test::run_cmd "group list"               "Groups"  group list
  cmd::test::run_cmd "group show --name family" "Peers:"  group show --name family
  cmd::test::run_cmd_fails "group show nonexistent"       group show --name nonexistent
}

function cmd::test::section_audit() {
  test::section "Audit"
  cmd::test::run_cmd_any "audit"                   "passed" audit
  cmd::test::run_cmd_any "audit --peer phone-nuno" "passed" audit --peer phone-nuno
  cmd::test::run_cmd_any "audit --type phone"      "passed" audit --type phone
}

function cmd::test::section_logs() {
  test::section "Logs"
  cmd::test::run_cmd "logs"                   "Activity" logs
  cmd::test::run_cmd "logs --name phone-nuno" "Activity" logs --name phone-nuno
  cmd::test::run_cmd "logs --fw"              "Firewall Drops" logs --fw
  cmd::test::run_cmd "logs --wg"              "WireGuard Events" logs --wg
}

function cmd::test::section_fw() {
  test::section "Firewall"
  cmd::test::run_cmd "fw list"                   "FORWARD"    fw list
  cmd::test::run_cmd "fw list --peer phone-nuno" ""           fw list --peer phone-nuno
  cmd::test::run_cmd "fw list --no-nflog"        ""           fw list --no-nflog
  cmd::test::run_cmd "fw list --no-accept"       ""           fw list --no-accept
  cmd::test::run_cmd "fw list --no-drop"         ""           fw list --no-drop
  cmd::test::run_cmd "fw nat"                    "PREROUTING" fw nat
  cmd::test::run_cmd "fw count"                  "TOTAL"      fw count
}

function cmd::test::section_net() {
  test::section "Net"
  "$WGCTL_BINARY" net rm --name test-svc --force > /dev/null 2>&1 || true

  cmd::test::run_cmd "net add service"     "added"    net add --name test-svc --ip 10.0.0.99 --desc "Test service"
  cmd::test::run_cmd "net add port"        "Added"    net add --name test-svc:web --port 9999:tcp
  cmd::test::run_cmd "net list"            "test-svc" net list
  cmd::test::run_cmd "net list --detailed" "web"      net list --detailed
  cmd::test::run_cmd "net show"            "9999"     net show --name test-svc
  cmd::test::run_cmd "net rm port"         "Removed"  net rm --name test-svc:web --force
  cmd::test::run_cmd "net add port again"  "Added"    net add --name test-svc:web --port 9999:tcp
  cmd::test::run_cmd "net rm all ports"    "Removed"  net rm --name test-svc:ports --force
  cmd::test::run_cmd "net rm service"      "Removed"  net rm --name test-svc --force
  cmd::test::run_cmd_fails "net show nonexistent"    net show --name nonexistent-svc
  cmd::test::run_cmd_fails "net add port no service" net add --name nonexistent:web --port 80:tcp
}

function cmd::test::section_subnet() {
  test::section "Subnet"
  "$WGCTL_BINARY" subnet rm --name test-subnet-2 > /dev/null 2>&1 || true
  "$WGCTL_BINARY" subnet rm --name test-subnet   > /dev/null 2>&1 || true

  cmd::test::run_cmd "subnet list"              "desktop"  subnet list
  cmd::test::run_cmd "subnet show desktop"      "tunnel:"  subnet show --name desktop
  cmd::test::run_cmd "subnet show guests group" "guests"    subnet show --name guests
  cmd::test::run_cmd_fails "subnet show nonexistent"       subnet show --name nonexistent

  cmd::test::run_cmd "subnet add" "added" \
    subnet add --name test-subnet --subnet 10.1.250.0/24 --type iot --desc "Test"
  cmd::test::run_cmd "subnet list shows new" "test-subnet" \
    subnet list
  cmd::test::run_cmd_fails "subnet rename in-use (desktop)" \
    subnet rename --name desktop --new-name workstation
  cmd::test::run_cmd "subnet rename unused" "renamed" \
    subnet rename --name test-subnet --new-name test-subnet-2
  cmd::test::run_cmd "subnet rm" "removed" \
    subnet rm --name test-subnet-2
  cmd::test::run_cmd_fails "subnet rm nonexistent" \
    subnet rm --name nonexistent-subnet
}

function cmd::test::section_identity() {
  test::section "Identity"
  cmd::test::run_cmd "identity list"              ""        identity list
  cmd::test::run_cmd "identity migrate --dry-run" "Dry run" identity migrate --dry-run
  cmd::test::run_cmd "identity show nuno"         "nuno"    identity show --name nuno
  cmd::test::run_cmd_fails "identity show nonexistent"      identity show --name nonexistent
}