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

SCRIPT_NAME="$(basename "$0")"
HOSTS_FILE="/etc/hosts"
DRY_RUN=0
REMOVE=0
CHECK=0
LIST=0

usage() {
    cat <<'EOF'
Usage: hosts-add.sh [options] IP HOSTNAME...

Add or remove hostnames in a hosts file. Existing entries are not duplicated.

Options:
  --file PATH  Hosts file to edit (default: /etc/hosts).
  --dry-run    Print planned changes without editing.
  --check      Check whether hostnames are present for the expected IP.
  --list       List non-comment entries from the hosts file.
  --remove     Remove matching hostnames instead of adding them.
  -h, --help   Show this help.
EOF
}

while (( $# > 0 )); do
    case "$1" in
        --file)
            (( $# >= 2 )) || { echo "--file requires a value." >&2; exit 2; }
            HOSTS_FILE="$2"
            shift
            ;;
        --dry-run) DRY_RUN=1 ;;
        --check) CHECK=1 ;;
        --list) LIST=1 ;;
        --remove) REMOVE=1 ;;
        -h|--help)
            usage
            exit 0
            ;;
        --) shift; break ;;
        -*) echo "Unknown option: $1" >&2; exit 2 ;;
        *) break ;;
    esac
    shift
done

if (( LIST == 1 )); then
    [[ $# -eq 0 ]] || { echo "--list does not take IP or hostname arguments." >&2; exit 2; }
    [[ -r "$HOSTS_FILE" ]] || { echo "Cannot read hosts file: $HOSTS_FILE" >&2; exit 1; }
    awk 'NF && $1 !~ /^#/ { print }' "$HOSTS_FILE"
    exit 0
fi

(( $# >= 2 )) || { usage >&2; exit 2; }
IP="$1"
shift
HOSTS=("$@")

[[ "$IP" =~ ^[0-9A-Fa-f:.]+$ ]] || { echo "Invalid IP address format: $IP" >&2; exit 2; }
for host in "${HOSTS[@]}"; do
    [[ "$host" =~ ^[A-Za-z0-9._-]+$ ]] || { echo "Invalid hostname: $host" >&2; exit 2; }
done

if (( CHECK == 1 && REMOVE == 1 )); then
    echo "--check and --remove cannot be used together." >&2
    exit 2
fi

if [[ "$HOSTS_FILE" == "/etc/hosts" && $EUID -ne 0 && $DRY_RUN -eq 0 && $CHECK -eq 0 ]]; then
    exec sudo -- "$0" --file "$HOSTS_FILE" ${REMOVE:+--remove} "$IP" "${HOSTS[@]}"
fi

if (( CHECK == 1 )); then
    [[ -r "$HOSTS_FILE" ]] || { echo "Cannot read hosts file: $HOSTS_FILE" >&2; exit 1; }
    missing=0
    for host in "${HOSTS[@]}"; do
        if awk -v ip="$IP" -v host="$host" '$1 == ip { for (i = 2; i <= NF; i++) if ($i == host) found = 1 } END { exit !found }' "$HOSTS_FILE"; then
            printf '[%s] OK %s -> %s\n' "$SCRIPT_NAME" "$host" "$IP"
        else
            printf '[%s] MISSING %s -> %s\n' "$SCRIPT_NAME" "$host" "$IP"
            missing=1
        fi
    done
    exit "$missing"
fi

tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT

if [[ ! -e "$HOSTS_FILE" ]]; then
    if (( DRY_RUN == 1 )); then
        : > "$tmp.source"
        SOURCE_FILE="$tmp.source"
    else
        touch "$HOSTS_FILE"
        SOURCE_FILE="$HOSTS_FILE"
    fi
else
    SOURCE_FILE="$HOSTS_FILE"
fi
awk -v remove="$REMOVE" -v hosts="${HOSTS[*]}" '
    BEGIN {
        n = split(hosts, wanted, " ")
        for (i = 1; i <= n; i++) remove_host[wanted[i]] = 1
    }
    /^[[:space:]]*#/ || NF == 0 { print; next }
    {
        out = $1
        changed = 0
        for (i = 2; i <= NF; i++) {
            if (remove_host[$i]) {
                changed = 1
                next
            }
            out = out " " $i
        }
        if (out != $1 || !changed) print out
    }
' "$SOURCE_FILE" > "$tmp"

if (( REMOVE == 0 )); then
    missing=()
    for host in "${HOSTS[@]}"; do
        if ! awk -v host="$host" '($1 !~ /^#/) { for (i = 2; i <= NF; i++) if ($i == host) found = 1 } END { exit !found }' "$tmp"; then
            missing+=("$host")
        fi
    done
    if (( ${#missing[@]} > 0 )); then
        printf '%s %s\n' "$IP" "${missing[*]}" >> "$tmp"
    fi
fi

if [[ -e "$HOSTS_FILE" ]] && cmp -s "$HOSTS_FILE" "$tmp"; then
    printf '[%s] Hosts file is already current: %s\n' "$SCRIPT_NAME" "$HOSTS_FILE"
    exit 0
fi

if (( DRY_RUN == 1 )); then
    printf '[%s] Would update: %s\n' "$SCRIPT_NAME" "$HOSTS_FILE"
    diff -u "$SOURCE_FILE" "$tmp" || true
    exit 0
fi

cp -a "$HOSTS_FILE" "$HOSTS_FILE.bak.$(date +%Y%m%d%H%M%S)"
chmod --reference="$HOSTS_FILE" "$tmp"
mv -f -- "$tmp" "$HOSTS_FILE"
printf '[%s] Updated: %s\n' "$SCRIPT_NAME" "$HOSTS_FILE"
