#!/bin/sh
set -eu

command_name=${1:-help}
provider_name=${2:-}

fail() {
  printf '%s\n' "Error: $*" >&2
  exit 1
}

provider_dns() {
  case "$1" in
    cloudflare) printf '%s\n' '1.1.1.1 1.0.0.1' ;;
    google) printf '%s\n' '8.8.8.8 8.8.4.4' ;;
    quad9) printf '%s\n' '9.9.9.9 149.112.112.112' ;;
    adguard) printf '%s\n' '94.140.14.14 94.140.15.15' ;;
    alidns) printf '%s\n' '223.5.5.5 223.6.6.6' ;;
    dnspod) printf '%s\n' '119.29.29.29' ;;
    baidu) printf '%s\n' '180.76.76.76' ;;
    *) fail "Unknown provider '$1'; run './forns.sh list'" ;;
  esac
}

list_providers() {
  printf '%-12s %s\n' cloudflare '1.1.1.1, 1.0.0.1'
  printf '%-12s %s\n' google '8.8.8.8, 8.8.4.4'
  printf '%-12s %s\n' quad9 '9.9.9.9, 149.112.112.112'
  printf '%-12s %s\n' adguard '94.140.14.14, 94.140.15.15'
  printf '%-12s %s\n' alidns '223.5.5.5, 223.6.6.6'
  printf '%-12s %s\n' dnspod '119.29.29.29'
  printf '%-12s %s\n' baidu '180.76.76.76'
}

require_root() {
  [ "$(id -u)" -eq 0 ] || fail 'Administrator privileges are required; run this command with sudo'
}

mac_active_service() {
  default_device=$(route -n get default 2>/dev/null | awk '/interface:/{print $2; exit}')
  [ -n "$default_device" ] || fail 'No active network service was found'
  networksetup -listnetworkserviceorder | awk -v device="$default_device" '
    /^\([0-9]+\)/ { service=$0; sub(/^\([0-9]+\) /,"",service) }
    $0 ~ "Device: " device "\\)" { print service; exit }
  '
}

linux_active_interfaces() {
  ip route show default 2>/dev/null | awk '{print $5}' | sort -u
}

linux_nm_uuids() {
  nmcli -t -f UUID,TYPE connection show --active | while IFS=: read -r uuid type; do
    case "$type" in
      802-3-ethernet|802-11-wireless|ethernet|wifi) printf '%s\n' "$uuid" ;;
    esac
  done
}

show_current() {
  case "$(uname -s)" in
    Darwin)
      service=$(mac_active_service)
      printf '%s  ' "$service"
      networksetup -getdnsservers "$service"
      ;;
    Linux)
      if command -v nmcli >/dev/null 2>&1; then
        for device in $(nmcli -t -f DEVICE,TYPE device status | awk -F: '$2=="ethernet"||$2=="wifi"{print $1}'); do
          printf '%s  ' "$device"
          nmcli -g IP4.DNS device show "$device" | paste -sd ', ' -
        done
      elif command -v resolvectl >/dev/null 2>&1; then
        resolvectl status
      else
        fail 'Neither NetworkManager nor systemd-resolved was found'
      fi
      ;;
    *) fail 'This system is not supported' ;;
  esac
}

set_dns() {
  require_root
  addresses=$(provider_dns "$provider_name")
  case "$(uname -s)" in
    Darwin)
      service=$(mac_active_service)
      networksetup -setdnsservers "$service" $addresses
      printf 'DNS updated on %s\n' "$service"
      ;;
    Linux)
      if command -v nmcli >/dev/null 2>&1; then
        uuids=$(linux_nm_uuids)
        [ -n "$uuids" ] || fail 'No active NetworkManager connection was found'
        for uuid in $uuids; do
          nmcli connection modify "$uuid" ipv4.ignore-auto-dns yes ipv4.dns "$addresses"
          nmcli connection up uuid "$uuid" >/dev/null
        done
        printf '%s\n' 'DNS updated on active NetworkManager connections'
      elif command -v resolvectl >/dev/null 2>&1; then
        interfaces=$(linux_active_interfaces)
        [ -n "$interfaces" ] || fail 'No active default-route interface was found'
        for interface in $interfaces; do resolvectl dns "$interface" $addresses; done
        printf '%s\n' 'DNS updated through systemd-resolved; this setting may reset after reboot'
      else
        fail 'Neither NetworkManager nor systemd-resolved was found; resolv.conf was not modified'
      fi
      ;;
    *) fail 'This system is not supported' ;;
  esac
}

restore_auto() {
  require_root
  case "$(uname -s)" in
    Darwin)
      service=$(mac_active_service)
      networksetup -setdnsservers "$service" Empty
      printf 'Automatic DNS restored on %s\n' "$service"
      ;;
    Linux)
      if command -v nmcli >/dev/null 2>&1; then
        uuids=$(linux_nm_uuids)
        [ -n "$uuids" ] || fail 'No active NetworkManager connection was found'
        for uuid in $uuids; do
          nmcli connection modify "$uuid" ipv4.ignore-auto-dns no ipv4.dns ''
          nmcli connection up uuid "$uuid" >/dev/null
        done
        printf '%s\n' 'Automatic DNS restored on active NetworkManager connections'
      elif command -v resolvectl >/dev/null 2>&1; then
        interfaces=$(linux_active_interfaces)
        [ -n "$interfaces" ] || fail 'No active default-route interface was found'
        for interface in $interfaces; do resolvectl revert "$interface"; done
        printf '%s\n' 'Automatic DNS restored through systemd-resolved'
      else
        fail 'Neither NetworkManager nor systemd-resolved was found'
      fi
      ;;
    *) fail 'This system is not supported' ;;
  esac
}

flush_cache() {
  require_root
  case "$(uname -s)" in
    Darwin) dscacheutil -flushcache; killall -HUP mDNSResponder; printf '%s\n' 'DNS cache cleared' ;;
    Linux)
      if command -v resolvectl >/dev/null 2>&1; then resolvectl flush-caches
      elif command -v systemd-resolve >/dev/null 2>&1; then systemd-resolve --flush-caches
      else fail 'No supported DNS cache service was found'
      fi
      printf '%s\n' 'DNS cache cleared'
      ;;
    *) fail 'This system is not supported' ;;
  esac
}

case "$command_name" in
  list) list_providers ;;
  current) show_current ;;
  set) [ -n "$provider_name" ] || fail 'A provider name is required'; set_dns ;;
  auto) restore_auto ;;
  flush) flush_cache ;;
  help|-h|--help)
    printf '%s\n' 'FORNS - Find your DNS' '' 'Usage:' \
      '  ./forns.sh list' \
      '  ./forns.sh current' \
      '  sudo ./forns.sh set <provider>' \
      '  sudo ./forns.sh auto' \
      '  sudo ./forns.sh flush'
    ;;
  *) fail "Unknown command '$command_name'" ;;
esac
