#!/usr/bin/env bash
set -Eeuo pipefail

readonly SCRIPT_NAME="$(basename "$0")"
readonly REPO_BASE_URL="https://raw.githubusercontent.com/trick77/nftables-blacklist/master"

readonly INSTALL_SCRIPT_PATH="/usr/local/sbin/update-blacklist.sh"
readonly CONFIG_DIR="/etc/nftables-blacklist"
readonly CONFIG_FILE="${CONFIG_DIR}/nftables-blacklist.conf"

readonly SYSTEMD_DIR="/etc/systemd/system"
readonly SERVICE_FILE="${SYSTEMD_DIR}/nftables-blacklist.service"
readonly UPDATE_SERVICE_FILE="${SYSTEMD_DIR}/nftables-blacklist-update.service"
readonly TIMER_FILE="${SYSTEMD_DIR}/nftables-blacklist-update.timer"

readonly SYSCTL_FILE="/etc/sysctl.d/99-nftables-blacklist.conf"

ACTION="install"
FORCE_CONFIG_DOWNLOAD="false"
SKIP_INITIAL_RUN="false"
CREATE_SYSCTL_TWEAK="false"

log()  { printf '[INFO] %s\n' "$*"; }
warn() { printf '[WARN] %s\n' "$*" >&2; }
err()  { printf '[ERROR] %s\n' "$*" >&2; }
die()  { err "$*"; exit 1; }

on_error() {
  local exit_code=$?
  err "Fehler in Zeile ${BASH_LINENO[0]} beim Ausführen von: ${BASH_COMMAND}"
  exit "$exit_code"
}
trap on_error ERR

usage() {
  cat <<EOF
Verwendung:
  $SCRIPT_NAME [install|uninstall] [optionen]

Aktionen:
  install               Installiert nftables-blacklist (Standard)
  uninstall             Entfernt nftables-blacklist sauber

Optionen:
  --force-config        Vorhandene Config durch Default-Config ersetzen
  --skip-initial-run    Initialen Lauf von update-blacklist.sh überspringen
  --with-sysctl         Erstellt optionalen Sysctl-Tweak für große Sets
  -h, --help            Hilfe anzeigen

Beispiele:
  $SCRIPT_NAME
  $SCRIPT_NAME install --with-sysctl
  $SCRIPT_NAME uninstall
EOF
}

require_root() {
  if [[ "${EUID}" -ne 0 ]]; then
    die "Bitte als root ausführen (z. B. mit sudo)."
  fi
}

require_command() {
  command -v "$1" >/dev/null 2>&1 || die "Benötigter Befehl fehlt: $1"
}

check_os() {
  if [[ -r /etc/os-release ]]; then
    # shellcheck disable=SC1091
    . /etc/os-release
    log "Erkanntes System: ${PRETTY_NAME:-unbekannt}"
  else
    warn "/etc/os-release nicht gefunden, OS-Prüfung übersprungen."
  fi
}

install_dependencies() {
  require_command apt
  log "Installiere Abhängigkeiten ..."
  apt update
  apt install -y curl iprange nftables
}

download_main_script() {
  log "Lade update-blacklist.sh herunter ..."
  curl -fsSL -o "$INSTALL_SCRIPT_PATH" \
    "${REPO_BASE_URL}/update-blacklist.sh"
  chmod 0755 "$INSTALL_SCRIPT_PATH"
}

prepare_config() {
  log "Erstelle Konfigurationsverzeichnis ..."
  mkdir -p "$CONFIG_DIR"
  chmod 0755 "$CONFIG_DIR"

  if [[ -f "$CONFIG_FILE" && "$FORCE_CONFIG_DOWNLOAD" != "true" ]]; then
    log "Config existiert bereits, Download wird übersprungen."
    return
  fi

  if [[ -f "$CONFIG_FILE" ]]; then
    local backup_file="${CONFIG_FILE}.bak.$(date +%Y%m%d-%H%M%S)"
    log "Sichere bestehende Config nach: $backup_file"
    cp -a "$CONFIG_FILE" "$backup_file"
  fi

  log "Lade Default-Config herunter ..."
  curl -fsSL -o "$CONFIG_FILE" \
    "${REPO_BASE_URL}/nftables-blacklist.conf"
  chmod 0644 "$CONFIG_FILE"
}

preflight_checks_install() {
  require_command curl
  require_command systemctl
  require_command nft

  if [[ ! -d /run/systemd/system ]]; then
    die "systemd scheint nicht aktiv zu sein. Dieses Skript erwartet ein systemd-basiertes System."
  fi

  if systemctl is-active --quiet firewalld 2>/dev/null; then
    warn "firewalld ist aktiv. Prüfe später, ob sich deine Regeln wie gewünscht verhalten."
  fi

  if systemctl is-active --quiet ufw 2>/dev/null; then
    warn "ufw ist aktiv. Prüfe später, ob die nftables-Regeln korrekt zusammenspielen."
  fi
}

run_initial_update() {
  if [[ "$SKIP_INITIAL_RUN" == "true" ]]; then
    log "Initialer Lauf wurde per Flag übersprungen."
    return
  fi

  log "Führe initiales Blacklist-Update aus ..."
  "$INSTALL_SCRIPT_PATH" "$CONFIG_FILE"
}

write_systemd_units() {
  log "Schreibe systemd-Service ..."
  cat > "$SERVICE_FILE" <<'EOF'
[Unit]
Description=nftables IP blacklist
After=network.target
Wants=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/update-blacklist.sh /etc/nftables-blacklist/nftables-blacklist.conf
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF

  log "Schreibe systemd-Update-Service ..."
  cat > "$UPDATE_SERVICE_FILE" <<'EOF'
[Unit]
Description=Update nftables IP blacklist
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/update-blacklist.sh --cron /etc/nftables-blacklist/nftables-blacklist.conf
EOF

  log "Schreibe systemd-Timer ..."
  cat > "$TIMER_FILE" <<'EOF'
[Unit]
Description=Update nftables IP blacklist daily

[Timer]
OnCalendar=*-*-* 23:33:00
Persistent=true
RandomizedDelaySec=14400

[Install]
WantedBy=timers.target
EOF

  chmod 0644 "$SERVICE_FILE" "$UPDATE_SERVICE_FILE" "$TIMER_FILE"
}

enable_units() {
  log "Lade systemd neu ..."
  systemctl daemon-reload

  log "Aktiviere nftables-blacklist.service ..."
  systemctl enable --now nftables-blacklist.service

  log "Aktiviere nftables-blacklist-update.timer ..."
  systemctl enable --now nftables-blacklist-update.timer
}

maybe_write_sysctl() {
  if [[ "$CREATE_SYSCTL_TWEAK" != "true" ]]; then
    return
  fi

  log "Schreibe optionalen Sysctl-Tweak für große nftables-Sets ..."
  cat > "$SYSCTL_FILE" <<'EOF'
net.core.rmem_max = 8388608
net.core.rmem_default = 8388608
EOF

  chmod 0644 "$SYSCTL_FILE"
  sysctl --system >/dev/null
}

post_install_info() {
  cat <<'EOF'

Fertig.

Nützliche Checks:
  sudo nft list table inet blacklist
  sudo nft list set inet blacklist blacklist4
  sudo nft list set inet blacklist blacklist6
  sudo nft list chain inet blacklist input
  systemctl list-timers nftables-blacklist-update

Hinweise:
  - AUTO_WHITELIST=yes in der Config ist empfehlenswert.
  - Für Container/Forwarding ggf. BLOCK_FORWARD=yes setzen.
  - Config bearbeiten:
      sudo nano /etc/nftables-blacklist/nftables-blacklist.conf

EOF
}

stop_disable_units() {
  log "Stoppe und deaktiviere Timer/Services, falls vorhanden ..."
  systemctl disable --now nftables-blacklist-update.timer 2>/dev/null || true
  systemctl disable --now nftables-blacklist.service 2>/dev/null || true
}

remove_nft_table() {
  if nft list table inet blacklist >/dev/null 2>&1; then
    log "Entferne nftables-Tabelle inet blacklist ..."
    nft delete table inet blacklist
  else
    log "Keine nftables-Tabelle inet blacklist vorhanden."
  fi
}

remove_files() {
  log "Entferne systemd-Unit-Dateien ..."
  rm -f "$SERVICE_FILE" "$UPDATE_SERVICE_FILE" "$TIMER_FILE"
  systemctl daemon-reload

  log "Entferne Installationsskript ..."
  rm -f "$INSTALL_SCRIPT_PATH"

  if [[ -d "$CONFIG_DIR" ]]; then
    log "Entferne Konfiguration unter $CONFIG_DIR ..."
    rm -rf "$CONFIG_DIR"
  else
    log "Kein Konfigurationsverzeichnis vorhanden."
  fi

  if [[ -f "$SYSCTL_FILE" ]]; then
    log "Entferne optionalen Sysctl-Tweak ..."
    rm -f "$SYSCTL_FILE"
    sysctl --system >/dev/null || true
  fi
}

post_uninstall_info() {
  cat <<'EOF'

Deinstallation abgeschlossen.

Optional manuell prüfen:
  sudo nft list tables
  systemctl list-unit-files | grep nftables-blacklist

EOF
}

parse_args() {
  while [[ $# -gt 0 ]]; do
    case "$1" in
      install)
        ACTION="install"
        shift
        ;;
      uninstall)
        ACTION="uninstall"
        shift
        ;;
      --force-config)
        FORCE_CONFIG_DOWNLOAD="true"
        shift
        ;;
      --skip-initial-run)
        SKIP_INITIAL_RUN="true"
        shift
        ;;
      --with-sysctl)
        CREATE_SYSCTL_TWEAK="true"
        shift
        ;;
      -h|--help)
        usage
        exit 0
        ;;
      *)
        die "Unbekanntes Argument: $1"
        ;;
    esac
  done
}

main_install() {
  require_root
  check_os
  preflight_checks_install
  install_dependencies
  download_main_script
  prepare_config
  run_initial_update
  write_systemd_units
  enable_units
  maybe_write_sysctl
  post_install_info
}

main_uninstall() {
  require_root
  require_command systemctl
  require_command nft

  stop_disable_units
  remove_nft_table
  remove_files
  post_uninstall_info
}

parse_args "$@"

case "$ACTION" in
  install)   main_install ;;
  uninstall) main_uninstall ;;
  *)         die "Ungültige Aktion: $ACTION" ;;
esac