#!/bin/sh
# Сброс сети после переподключения Android-хотспота (macOS).
#
# RCA (почему старый recover не помогал):
#   - Ядерный сброс (route flush, ifconfig down) выполнялся только при sudo -n (passwordless).
#   - Без NOPASSWD в sudoers скрипт делал лишь toggle Wi‑Fi — ARP REJECT и битые маршруты оставались.
#   - Manual IPv4 на Wi‑Fi (Joplin 10.219.227.100) конфликтует с новой подсетью хотспота (10.36.x).
#
# Решение:
#   - Wi‑Fi всегда DHCP (правильный шлюз после reconnect).
#   - Joplin: вторичный IP (alias) *.100 в текущей подсети — не трогает default route.
#   - recover всегда запрашивает sudo (пароль) для полного сброса.
#
# Из любой папки (одна команда, чинит интернет + Joplin alias):
#   bash /Users/eternal/code/local_machine/docs/connectivity/scripts/macos-hotspot-network-recover.sh recover
#
# Переменные:
#   HOTSPOT_JOPLIN_MODE=alias|manual|off   alias (по умолчанию при ~/.config/joplin-webdav/env), manual, off
#   HOTSPOT_RECOVER_SKIP_JOPLIN=1          не трогать Joplin alias
#   JOPLIN_STATIC_IP                       последний октет .100 из env; подсеть — текущая
#   MACOS_PAC_SERVICES, WIFI_DEVICE

ROOT="$(cd "$(dirname "$0")" && pwd)"
PAC_SCRIPT="$ROOT/macos-system-pac.sh"
JOPLIN_ENV="${JOPLIN_WEBDAV_CONFIG:-$HOME/.config/joplin-webdav/env}"
JOPLIN_ALIAS_SCRIPT="${JOPLIN_ALIAS_SCRIPT:-$(cd "$ROOT/../../apps/joplin/scripts" 2>/dev/null && pwd)/joplin-hotspot-alias.sh}"
SCRIPT_SELF="$ROOT/$(basename "$0")"

wifi_device() {
  if [ -n "${WIFI_DEVICE:-}" ]; then
    printf '%s\n' "$WIFI_DEVICE"
    return
  fi
  networksetup -listallhardwareports 2>/dev/null \
    | awk '/Hardware Port: Wi-Fi$/ {p=1; next} p && /^Device: / {print $2; exit}'
}

ipv4_subnet() {
  printf '%s\n' "$1" | awk -F. '{print $1"."$2"."$3}'
}

ipv4_last_octet() {
  printf '%s\n' "$1" | awk -F. '{print $4}'
}

load_joplin_env() {
  if [ -f "$JOPLIN_ENV" ]; then
    # shellcheck disable=SC1090
    . "$JOPLIN_ENV"
  fi
}

wifi_config_method() {
  networksetup -getinfo "Wi-Fi" 2>/dev/null | awk 'NR==1 {print $1; exit}'
}

wifi_manual_router() {
  networksetup -getinfo "Wi-Fi" 2>/dev/null | awk -F': ' '/^Router: / {print $2; exit}'
}

wifi_manual_ip() {
  networksetup -getinfo "Wi-Fi" 2>/dev/null | awk -F': ' '/^IP address: / {print $2; exit}'
}

default_gateway() {
  route -n get default 2>/dev/null | awk '/gateway:/{print $2; exit}'
}

joplin_mode() {
  if [ "${HOTSPOT_RECOVER_SKIP_JOPLIN:-0}" = "1" ]; then
    echo off
    return
  fi
  if [ -n "${HOTSPOT_JOPLIN_MODE:-}" ]; then
    echo "$HOTSPOT_JOPLIN_MODE"
    return
  fi
  # mDNS (joplin-mac.local) — URL на телефоне не зависит от подсети; alias не нужен
  echo off
}

joplin_target_ip() {
  load_joplin_env
  gw="$(default_gateway 2>/dev/null || true)"
  [ -n "$gw" ] || return 1
  subnet="$(ipv4_subnet "$gw")"
  if [ -n "${JOPLIN_STATIC_IP:-}" ]; then
    last="$(ipv4_last_octet "$JOPLIN_STATIC_IP")"
  else
    last=100
  fi
  printf '%s\n' "${subnet}.${last}"
}

list_en0_alias_ips() {
  dev="${1:-en0}"
  ifconfig "$dev" 2>/dev/null | awk '/inet / && /alias/ {print $2}'
}

sudo_run() {
  echo "   \$ sudo $*"
  sudo "$@"
}

ensure_dhcp_wifi() {
  method="$(wifi_config_method 2>/dev/null || true)"
  if [ "$method" = "Manual" ]; then
    echo "   Wi‑Fi Manual $(wifi_manual_ip) → $(wifi_manual_router) — переводим на DHCP..."
    networksetup -setdhcp "Wi-Fi" 2>/dev/null || sudo_run networksetup -setdhcp "Wi-Fi"
  else
    echo "   Wi‑Fi уже DHCP (или не Manual)."
  fi
}

nuclear_network_reset() {
  dev="$1"
  echo "   Ядерный сброс на $dev (нужен пароль sudo)..."
  sudo_run ifconfig "$dev" down
  sudo_run route -n flush
  sudo_run dscacheutil -flushcache
  sudo_run killall -HUP mDNSResponder
  sudo_run ifconfig "$dev" up
  sudo_run ipconfig set "$dev" DHCP
}

wait_for_connectivity() {
  dev="$1"
  i=0
  while [ "$i" -lt 30 ]; do
    gw="$(default_gateway 2>/dev/null || true)"
    ip="$(ipconfig getifaddr "$dev" 2>/dev/null || true)"
    if [ -n "$gw" ] && [ -n "$ip" ]; then
      if ping -c 1 -W 1500 "$gw" >/dev/null 2>&1; then
        echo "   Связность OK: IP=$ip gateway=$gw (${i}s)"
        return 0
      fi
    fi
    sleep 1
    i=$((i + 1))
  done
  echo "   WARN: gateway/IP не поднялись за 30с — проверьте captive portal" >&2
  return 1
}

remove_joplin_aliases() {
  dev="$1"
  target="$2"
  for ip in $(list_en0_alias_ips "$dev"); do
    case "$ip" in
      *.*.*.100|"$target")
        echo "   Удаляем alias $ip на $dev"
        sudo_run ifconfig "$dev" inet "$ip" -alias 2>/dev/null || true
        ;;
    esac
  done
}

setup_joplin_alias() {
  dev="$1"
  target="$(joplin_target_ip 2>/dev/null || true)"
  [ -n "$target" ] || { echo "   Joplin alias: нет gateway" >&2; return 1; }
  primary="$(ipconfig getifaddr "$dev" 2>/dev/null || true)"
  if [ "$primary" = "$target" ]; then
    echo "   Joplin: primary IP уже $target"
    return 0
  fi
  remove_joplin_aliases "$dev" "$target"
  if ifconfig "$dev" 2>/dev/null | grep -q "inet $target "; then
    echo "   Joplin alias $target уже на $dev"
    return 0
  fi
  echo "   Joplin alias: $target/24 на $dev (primary DHCP: ${primary:-?})"
  sudo_run ifconfig "$dev" inet "$target" netmask 255.255.255.0 alias
  echo "   URL на телефоне: http://${target}:8080"
  if [ -f "$JOPLIN_ENV" ] && [ -n "${JOPLIN_STATIC_IP:-}" ]; then
    cur_sub="$(ipv4_subnet "$target")"
    env_sub="$(ipv4_subnet "$JOPLIN_STATIC_IP")"
    if [ "$cur_sub" != "$env_sub" ]; then
      echo "   ⚠ Подсеть сменилась ($env_sub.x → $cur_sub.x) — обновите URL в Joplin на телефоне"
    fi
  fi
}

setup_joplin_manual() {
  gw="$(default_gateway 2>/dev/null || true)"
  [ -n "$gw" ] || return 1
  target="$(joplin_target_ip 2>/dev/null || true)"
  echo "   Joplin manual (legacy): $target → router $gw"
  networksetup -setmanual "Wi-Fi" "$target" 255.255.255.0 "$gw"
}

print_manual_ipv4_status() {
  info="$(networksetup -getinfo "Wi-Fi" 2>/dev/null || true)"
  echo "$info" | head -5
  method="$(printf '%s\n' "$info" | awk 'NR==1 {print $1; exit}')"
  dev="$(wifi_device 2>/dev/null || echo en0)"
  current_ip="$(ipconfig getifaddr "$dev" 2>/dev/null || true)"
  gw="$(default_gateway 2>/dev/null || true)"
  echo "--- runtime $dev ---"
  echo "primary IP: ${current_ip:-(none)}"
  echo "default gateway: ${gw:-(none)}"
  aliases="$(list_en0_alias_ips "$dev" | tr '\n' ' ')"
  if [ -n "$aliases" ]; then
    echo "aliases: $aliases"
  fi
  if [ -f "$JOPLIN_ENV" ]; then
    target="$(joplin_target_ip 2>/dev/null || true)"
    echo "joplin target: ${target:-?} (mode: $(joplin_mode))"
    if [ -n "$target" ] && ! ifconfig "$dev" 2>/dev/null | grep -q "inet $target "; then
      echo "⚠ Joplin IP $target не на интерфейсе — телефон не достучится"
    fi
  fi
  if [ "$method" = "Manual" ]; then
    manual_ip="$(printf '%s\n' "$info" | awk -F': ' '/^IP address: / {print $2; exit}')"
    manual_router="$(printf '%s\n' "$info" | awk -F': ' '/^Router: / {print $2; exit}')"
    if [ -n "$manual_router" ] && [ -n "$gw" ]; then
      if [ "$(ipv4_subnet "$manual_router")" != "$(ipv4_subnet "$gw")" ]; then
        echo "⚠ MISMATCH: Manual router $(ipv4_subnet "$manual_router").x vs gateway $(ipv4_subnet "$gw").x"
        echo "  → запустите: bash $SCRIPT_SELF recover"
      elif [ -n "$current_ip" ] && [ "$current_ip" != "$manual_ip" ]; then
        echo "⚠ MISMATCH: Manual IP $manual_ip, runtime $current_ip"
      fi
    fi
  fi
  if [ -n "$gw" ]; then
    echo "--- gateway $gw ---"
    arp -n "$gw" 2>/dev/null || true
    route -n get "$gw" 2>/dev/null | grep -E 'interface|flags' || true
  fi
}

usage() {
  cat <<EOF
Usage: $(basename "$0") status|recover

  recover — PAC/SOCKS off, DHCP, ядерный сброс (sudo), Joplin alias *.100
  status  — диагностика

Полная команда из любой папки:
  bash $SCRIPT_SELF recover
EOF
  exit 1
}

[ "$#" -eq 1 ] || usage
cmd=$1

case "$cmd" in
  status)
    echo "=== Wi-Fi device ==="
    wifi_device || true
    echo "=== IPv4 ==="
    print_manual_ipv4_status || true
    echo "=== PAC ==="
    if [ -x "$PAC_SCRIPT" ]; then
      MACOS_PAC_SERVICES="${MACOS_PAC_SERVICES:-Wi-Fi|Thunderbolt Bridge 2}" "$PAC_SCRIPT" status || true
    fi
    echo "=== SOCKS (Wi-Fi) ==="
    networksetup -getsocksfirewallproxy "Wi-Fi" 2>/dev/null || true
    echo "=== PAC listener :1089 ==="
    lsof -i :1089 -sTCP:LISTEN 2>/dev/null | head -3 || echo "(none)"
    echo "=== Default route ==="
    route -n get default 2>/dev/null | head -8 || echo "(no default route)"
    gw="$(default_gateway 2>/dev/null || true)"
    if [ -n "$gw" ]; then
      ping -c 1 -W 2000 "$gw" >/dev/null 2>&1 && echo "ping gateway: ok" || echo "ping gateway: fail"
    fi
    curl -sS --max-time 5 -o /dev/null https://1.1.1.1 2>/dev/null && echo "curl: ok" || echo "curl: fail"
    ;;
  recover)
    echo "=== Hotspot recover (DHCP + route flush) ==="
    echo "Скрипт запросит пароль sudo для сброса маршрутов."
    echo ""
    echo "1) PAC off..."
    if [ -x "$PAC_SCRIPT" ]; then
      MACOS_PAC_SERVICES="${MACOS_PAC_SERVICES:-Wi-Fi|Thunderbolt Bridge 2}" "$PAC_SCRIPT" off
    fi
    echo "2) SOCKS off..."
    networksetup -setsocksfirewallproxystate "Wi-Fi" off 2>/dev/null || true
    echo "3) Wi‑Fi → DHCP (снимаем конфликтный Manual Joplin)..."
    ensure_dhcp_wifi
    dev="$(wifi_device)"
    if [ -z "$dev" ]; then
      echo "Ошибка: Wi‑Fi device не найден" >&2
      exit 1
    fi
    echo "4) Ядерный сброс $dev..."
    nuclear_network_reset "$dev"
    echo "5) Ожидание DHCP..."
    wait_for_connectivity "$dev" || true
    mode="$(joplin_mode)"
    case "$mode" in
      alias)
        echo "6) Joplin: alias IP (не ломает маршрутизацию)..."
        if [ -x "$JOPLIN_ALIAS_SCRIPT" ]; then
          "$JOPLIN_ALIAS_SCRIPT" ensure || true
        else
          setup_joplin_alias "$dev" || true
        fi
        ;;
      manual)
        echo "6) Joplin: manual Wi‑Fi (legacy)..."
        setup_joplin_manual || true
        ;;
      *)
        echo "6) Joplin: пропуск (нет $JOPLIN_ENV)"
        ;;
    esac
    echo ""
    echo "=== Итог ==="
    "$SCRIPT_SELF" status
    echo ""
    echo "Captive portal: http://neverssl.com"
    ;;
  *)
    usage
    ;;
esac
