#!/usr/bin/env bash
#
# check_redirects.sh - Follow and report HTTP redirect chains (max 10 hops).
#
# Run straight from a gist (it will ask for the URLs):
#   bash <(curl -fsSL https://gist.veru.se/Veruse/87653b8402024210986ecec3c826e1b9/raw/HEAD/redirect-checker.sh)
#   curl -fsSL https://gist.veru.se/Veruse/87653b8402024210986ecec3c826e1b9/raw/HEAD/redirect-checker.sh | bash
#
# Arguments/flags work if you want to skip the prompts:
#   bash <(curl -fsSL URL) https://a.com https://b.com
#   bash <(curl -fsSL URL) -f urls.txt

set -uo pipefail

MAX_REDIRECTS=10
TIMEOUT=10
CSV=0
URL_FILE=""
USER_AGENT="Mozilla/5.0 (redirect-checker)"

if [[ -t 1 ]]; then
  RED=$'\e[31m'; GREEN=$'\e[32m'; YELLOW=$'\e[33m'; CYAN=$'\e[36m'; BOLD=$'\e[1m'; RESET=$'\e[0m'
else
  RED=""; GREEN=""; YELLOW=""; CYAN=""; BOLD=""; RESET=""
fi

usage() {
  cat <<'EOF'
Usage: check_redirects.sh [options] [url ...]
  (no URLs given = interactive prompts)

Options:
  -f FILE   Read URLs from FILE (one per line, # comments and blank lines ignored)
  -m N      Max redirects to follow (default: 10)
  -t SECS   Timeout per request in seconds (default: 10)
  -c        Output CSV (url,final_url,final_status,hops,result)
  -h        Show help
EOF
  exit "${1:-0}"
}

while getopts ":f:m:t:ch" opt; do
  case "$opt" in
    f) URL_FILE="$OPTARG" ;;
    m) MAX_REDIRECTS="$OPTARG" ;;
    t) TIMEOUT="$OPTARG" ;;
    c) CSV=1 ;;
    h) usage 0 ;;
    :) echo "Option -$OPTARG requires an argument." >&2; usage 1 ;;
    \?) echo "Unknown option: -$OPTARG" >&2; usage 1 ;;
  esac
done
shift $((OPTIND - 1))

command -v curl >/dev/null 2>&1 || { echo "Error: curl is required." >&2; exit 1; }

# ---- Collect URLs ----
URLS=()
if [[ -n "$URL_FILE" ]]; then
  [[ -r "$URL_FILE" ]] || { echo "Error: cannot read $URL_FILE" >&2; exit 1; }
  while IFS= read -r line || [[ -n "$line" ]]; do URLS+=("$line"); done < "$URL_FILE"
fi
URLS+=("$@")

# ---- Interactive prompts (read from the terminal, so it works with curl | bash) ----
TTY="${TTY_DEVICE:-/dev/tty}"
if [[ ${#URLS[@]} -eq 0 ]]; then
  if ! { exec 3<"$TTY"; } 2>/dev/null; then
    echo "Error: no URLs given and no terminal available to ask for them." >&2
    usage 1
  fi

  echo "${BOLD}Redirect checker${RESET} (max $MAX_REDIRECTS redirects per URL)"
  echo "  1) Check a single URL"
  echo "  2) Paste a list of URLs"
  echo "  3) Load URLs from a file"
  read -r -u 3 -p "Choose [1-3]: " choice

  case "$choice" in
    1)
      read -r -u 3 -p "URL: " line
      URLS+=("$line")
      ;;
    2)
      echo "Paste URLs, one per line. Press Enter on an empty line when done:"
      while IFS= read -r -u 3 line; do
        line="${line//$'\r'/}"
        [[ -z "${line// /}" ]] && break
        URLS+=("$line")
      done
      ;;
    3)
      read -r -u 3 -p "File path: " URL_FILE
      URL_FILE="${URL_FILE/#\~/$HOME}"
      [[ -r "$URL_FILE" ]] || { echo "Error: cannot read $URL_FILE" >&2; exit 1; }
      while IFS= read -r line || [[ -n "$line" ]]; do URLS+=("$line"); done < "$URL_FILE"
      ;;
    *)
      echo "Invalid choice." >&2; exit 1 ;;
  esac

  read -r -u 3 -p "Save results as CSV instead of on-screen output? [y/N]: " ans
  if [[ "$ans" =~ ^[Yy] ]]; then
    read -r -u 3 -p "CSV file name [redirects.csv]: " csvfile
    csvfile="${csvfile:-redirects.csv}"
    CSV=1
    echo "Writing results to $csvfile ..."
    exec >"$csvfile"
  fi
  exec 3<&-
fi

[[ ${#URLS[@]} -eq 0 ]] && { echo "No URLs provided." >&2; exit 1; }

csv_escape() { local s="${1//\"/\"\"}"; printf '"%s"' "$s"; }

# ---- Check a single URL ----
check_url() {
  local start="$1" current="$1" hops=0 result="" code="" next=""
  declare -A seen=()

  [[ "$current" =~ ^https?:// ]] || current="http://$current"
  [[ $CSV -eq 0 ]] && printf '\n%s%s%s\n' "$BOLD" "$start" "$RESET"

  while :; do
    local out
    out=$(curl -s -o /dev/null --max-time "$TIMEOUT" -A "$USER_AGENT" \
          -w '%{http_code} %{redirect_url}' "$current" 2>/dev/null)
    code="${out%% *}"
    next="${out#* }"; [[ "$next" == "$out" ]] && next=""

    if [[ -z "$code" || "$code" == "000" ]]; then
      result="ERROR (connection failed/timeout)"
      [[ $CSV -eq 0 ]] && printf '  [%2d] %sERR%s  %s\n' "$hops" "$RED" "$RESET" "$current"
      break
    fi

    local colour="$GREEN"
    [[ "$code" =~ ^3 ]] && colour="$YELLOW"
    [[ "$code" =~ ^[45] ]] && colour="$RED"
    [[ $CSV -eq 0 ]] && printf '  [%2d] %s%s%s  %s\n' "$hops" "$colour" "$code" "$RESET" "$current"

    seen["$current"]=1

    # Not a redirect (or redirect without Location) -> done
    if [[ ! "$code" =~ ^3 || -z "$next" ]]; then
      if [[ "$code" =~ ^2 ]]; then result="OK"
      elif [[ "$code" =~ ^3 ]]; then result="REDIRECT WITHOUT LOCATION"
      else result="HTTP $code"; fi
      break
    fi

    if [[ -n "${seen[$next]:-}" ]]; then
      result="LOOP DETECTED"
      [[ $CSV -eq 0 ]] && printf '       %s↻ loops back to %s%s\n' "$RED" "$next" "$RESET"
      current="$next"; hops=$((hops + 1))
      break
    fi

    hops=$((hops + 1))
    current="$next"

    if (( hops > MAX_REDIRECTS )); then
      result="TOO MANY REDIRECTS (>$MAX_REDIRECTS)"
      break
    fi
  done

  if [[ $CSV -eq 1 ]]; then
    printf '%s,%s,%s,%s,%s\n' "$(csv_escape "$start")" "$(csv_escape "$current")" \
      "$code" "$hops" "$(csv_escape "$result")"
  else
    local rc="$GREEN"; [[ "$result" != "OK" ]] && rc="$RED"
    printf '  %s→ %s%s | hops: %d | final: %s\n' "$rc" "$result" "$RESET" "$hops" "$current"
  fi

  [[ "$result" == "OK" ]]
}

# ---- Main ----
[[ $CSV -eq 1 ]] && echo "url,final_url,final_status,hops,result"

total=0; failed=0
for raw in "${URLS[@]}"; do
  url="$(echo "$raw" | tr -d '\r' | xargs)"   # trim whitespace / CRLF
  [[ -z "$url" || "$url" == \#* ]] && continue
  total=$((total + 1))
  check_url "$url" || failed=$((failed + 1))
done

if [[ $CSV -eq 0 ]]; then
  printf '\n%sChecked %d URL(s): %d OK, %d with issues%s\n' "$CYAN" "$total" "$((total - failed))" "$failed" "$RESET"
fi

(( failed == 0 ))