#!/bin/bash
# shellcheck disable=SC2015
set -e

init_logging() {
  if [ ! -d /var/log/garuda/ ]; then
    # shellcheck disable=SC2174
    mkdir -p -m 755 /var/log/garuda/
  fi
  echo -e "\n>-<->-< garuda-update at $(date +"%Y-%m-%d %R %Z(%:::z)")\n" >>/var/log/garuda/garuda-update
  exec &> >(stdbuf -i0 -o0 -e0 tee >(sed '/\x1b\[[0-9][EF]/d;/\r[^\n]$/d;s,\x1B\[[0-9;]*[a-zA-Z],,g' >>/var/log/garuda/garuda-update))
}

launch_health() {
  if [ -x /usr/bin/garuda-health ]; then
    # Exits with error code 1 if at least one LOW or above severity issue is found
    # We do not want this
    /usr/bin/garuda-health --forcetty || true
  fi
}

parse_pacman_log() {
  sed -i -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2};?)?)?[mGK]//g" "$AUTOPACMAN_LOG"
  local reason
  reason="$(tac "$AUTOPACMAN_LOG" | grep -oP -m 1 '(?<=error: failed to commit transaction \().*(?=\))')"
  case "$reason" in
  # Failed because of a corrupt package? Let's retry!
  "invalid or corrupted package"*)
    RETRY="Some corrupt downloads were detected. Retrying..."
    # Let's retry it with packages directly from the source. Maybe the mirror is misconfigured?
    CUSTOM_PACMAN_CONFIG="$(mktemp)"
    # shellcheck disable=2016
    sed 's|Include = /etc/pacman.d/chaotic-mirrorlist|Server = https://secret-mirror.chaotic.cx/$repo/$arch|g' /etc/pacman.conf >"$CUSTOM_PACMAN_CONFIG"
    pacman_args+=("--config" "$CUSTOM_PACMAN_CONFIG")
    ;;
  "download library error")
    RETRY="Some downloads failed. Retrying..."
    # Let's retry it with a single download connection, maybe we are overloading the machines network connection?
    CUSTOM_PACMAN_CONFIG="$(mktemp)"
    sed '/^ParallelDownloads.*/d' /etc/pacman.conf >"$CUSTOM_PACMAN_CONFIG"
    pacman_args+=("--config" "$CUSTOM_PACMAN_CONFIG")
    ;;
  "conflicting files")
    tac "$AUTOPACMAN_LOG" | gawk 'BEGIN { exitcode=1 }
            /error: failed to commit transaction \(conflicting files\)/ { exit exitcode }
            /\S+: (.*) exists in filesystem/ { if ($0 ~ /\S+: \/usr\/lib\/python[^\/]+\/site-packages\/[^/]+\/__pycache__\/.+\.pyc exists in filesystem/) { exitcode=0 } else { exit 1 } }
            ENDFILE {exit 1}' && pacman_args+=("--overwrite" "/usr/lib/python*/site-packages/*/__pycache__/*.pyc") && RETRY="Overwriting python cache file conflicts..." || true
    ;;
  esac
}

update_mirrorlist() {
  if [[ -v SKIP_MIRRORLIST ]]; then
    return
  fi

  local MIRRORLIST_TEMP MINLINES=0
  MIRRORLIST_TEMP="$(mktemp)"
  if command -v rate-mirrors >/dev/null; then
    MINLINES=10
    echo -e "\n\033[1;33m-->\033[1;34m Refreshing mirrorlists using rate-mirrors, please be patient..🍵\033[0m"
    # Refresh mirrorlist and make sure it actually contains content. There is a bug in rate-mirrors that creates empty files sometimes.
    rate-mirrors --allow-root --save="$MIRRORLIST_TEMP" arch --max-delay=21600 >/dev/null || { rm "$MIRRORLIST_TEMP"; }
    $INT
  elif command -v reflector >/dev/null; then
    MINLINES=5
    echo -e "\n\033[1;33m-->\033[1;34m Refreshing mirrorlists using reflector, please be patient..🍵\033[0m"
    reflector --latest 10 --age 2 --fastest 10 --protocol https --sort rate --save "$MIRRORLIST_TEMP" || { rm "$MIRRORLIST_TEMP"; }
    $INT
  else
    return
  fi

  if [ ! -f "$MIRRORLIST_TEMP" ]; then
    echo -e "\033[1;33mFailed to update mirrorlist. (The Arch Linux project's mirrorlist service is affected by DDoS attacks. The first connection from your device's IP address may fail. This can be safely ignored.)\n\033[0m"
    return
  fi

  if COUNT="$(grep -Ec "^Server *=" "$MIRRORLIST_TEMP")" && [ "$COUNT" -ge "$MINLINES" ]; then
    install -m644 "$MIRRORLIST_TEMP" /etc/pacman.d/mirrorlist
    DATABASE_UPDATED="force"
  else
    echo -e "\033[1;31mNew mirrorlist does not contain enough mirrors. Skipping mirrorlist update.\033[0m"
  fi

  # Newline to separate the context
  echo

  rm "$MIRRORLIST_TEMP"
}

do_update() {
  if [ -x /usr/bin/expect ]; then
    local AUTOPACMAN_LOG EXIT=0 RETRY=false CUSTOM_PACMAN_CONFIG="" SUCCESS=false
    if [ ! -v AUTOPACMAN_CONFLICTSFILE ]; then
      local AUTOPACMAN_CONFLICTSFILE
      AUTOPACMAN_CONFLICTSFILE="$(mktemp -u)"
    fi
    AUTOPACMAN_LOG="$(mktemp)"

    # Try to run the update
    LANG=C LANGUAGE=C LC_ALL=C AUTOPACMAN_LOG="$AUTOPACMAN_LOG" AUTOPACMAN_NO_SPACE_CHECK="$NO_SPACE_CHECK" AUTOPACMAN_PACMAN_NOCONFIRM="$PACMAN_NOCONFIRM" AUTOPACMAN_CONFLICTSFILE="$AUTOPACMAN_CONFLICTSFILE" /usr/lib/garuda/garuda-update/auto-pacman "$PACMAN" "${pacman_args[@]}" || { EXIT=$?; }

    # We have no reason not to get rid of the custom pacman config right away
    if [ -n "$CUSTOM_PACMAN_CONFIG" ]; then rm "$CUSTOM_PACMAN_CONFIG"; fi

    if [ "$EXIT" == "134" ] || [ "$EXIT" == "0" ]; then
      SUCCESS=true
    fi

    # If it failed, we take a look at the logfile to find out why
    if [ "$SUCCESS" != "true" ] && [ -z "$ALREADY_RETRIED" ]; then
      parse_pacman_log
    fi
    rm "$AUTOPACMAN_LOG"
    # We retry, as long as we haven't retried before
    if [ "$RETRY" != "false" ]; then
      echo -e "\n\033[1;33m-->\033[1;34m $RETRY \n\033[0m"
      ALREADY_RETRIED=true do_update
      return
    fi
    if [ -v AUTOPACMAN_CONFLICTSFILE ]; then
      rm -f "$AUTOPACMAN_CONFLICTSFILE"
      unset AUTOPACMAN_CONFLICTSFILE
    fi
    # Still no luck after a retry? Exit out, we can't deal with this.
    if [ "$SUCCESS" == "false" ]; then
      false
    fi
  else
    $PACMAN "${pacman_args[@]}"
  fi
}

show_changelog() {
  # Show update notices if the notices file exists
  if [ -e "/var/lib/garuda/tmp/update_notices" ]; then
    echo -e "\033[1;32mUpdate notices:\n\033[1;34m$(gawk -F '\t' '{print $2}' /var/lib/garuda/tmp/update_notices)\n\033[0m"
    rm /var/lib/garuda/tmp/update_notices
    return 0
  fi
  return 1
}

if [ -f /etc/garuda/garuda-update/config ]; then
  # shellcheck disable=SC1091
  source /etc/garuda/garuda-update/config
fi

# Parse CLI options
PARAMETERS=("$@")
PARSED_OPTIONS=$(getopt --options="ah" --longoptions="help,aur,skip-mirrorlist,no-space-check,noconfirm" --name "$0" -- "${PARAMETERS[@]}")
# shellcheck disable=SC2181
if [[ $? -ne 0 ]]; then
  echo -e "\033[1;31m\nFailed to parse CLI options\n\033[0m"
fi
eval set -- "$PARSED_OPTIONS"
while true; do
  case "$1" in
  -h | --help)
    # shellcheck source=help
    source /usr/lib/garuda/garuda-update/help
    ;;
  -a | --aur)
    UPDATE_AUR=1
    shift
    ;;
  --skip-mirrorlist)
    SKIP_MIRRORLIST=1
    shift
    ;;
  --no-space-check)
    NO_SPACE_CHECK=1
    shift
    ;;
  --noconfirm)
    PACMAN_NOCONFIRM=1
    shift
    ;;
  --)
    shift
    # If argc == 1 and the argument is empty as well as $GARUDA_UPDATE_SELFUPDATE == 1, assume no extra arguments
    # This is code to handle a bug in ancient versions of garuda-update
    if [ "$GARUDA_UPDATE_SELFUPDATE" == 1 ] && [ "$#" -eq 1 ] && [ -z "$1" ]; then
      break
    fi
    PACMAN_EXTRA_OPTS+=("${@}")
    break
    ;;
  *)
    echo "Programming error"
    exit 3
    ;;
  esac
done

# We do not use is-snapshot-boot here on purpose because old systems that just now pull in a new garuda-update do not have it yet.
if grep -qE 'subvol=@/.snapshots/[0-9]+/snapshot' /proc/cmdline && [[ ! -v GARUDA_SNAPSHOT_PACMAN ]]; then
  echo -e "\033[1;31mError: You are currently booted into a snapshot. Please restore the snapshot via btrfs-assistant or snapper-tools before updating your system.\n\033[1;34mNote: You can ignore this error by setting GARUDA_SNAPSHOT_PACMAN: GARUDA_SNAPSHOT_PACMAN=1 garuda-update\n\033[1;31mAny modifications made to this snapshot will be lost next reboot. ❌\033[0m"
  exit 1
fi

# We should start writing our log file here now, we're starting to modify the system
init_logging

# Add garuda repo if it doesn't exist
/usr/lib/garuda/garuda-update/update-helper-scripts migrate-garuda-repo && DATABASE_UPDATED=false || true

# Update mirrorlist and recheck if we need to update garuda-update
update_mirrorlist
self_update "${PARAMETERS[@]}"

# Run pre-update routines and hotfixes
/usr/lib/garuda/garuda-update/update-helper-scripts pre-update-routines || { if [ "$?" -eq 2 ]; then self_update "${PARAMETERS[@]}"; fi; }

pacman_args=("-Su")
if [ "$DATABASE_UPDATED" == false ]; then pacman_args+=("-y"); elif [ "$DATABASE_UPDATED" == "force" ]; then pacman_args+=("-yy"); fi
while IFS= read -r line; do
  pacman_args+=("$line")
done < <(/usr/lib/garuda/garuda-update/update-helper-scripts package-replaces)

if [ -v PACMAN_EXTRA_OPTS ]; then
  pacman_args+=("${PACMAN_EXTRA_OPTS[@]}")
fi

do_update

/usr/lib/garuda/garuda-update/update-helper-scripts post-update-routines

if [[ -v UPDATE_AUR ]] && [[ -z "$GARUDA_UPDATE_RANI" ]]; then
  # Check for AUR helper
  if [ -x /usr/bin/paru ] && [[ -n "$SUDO_UID" ]]; then
    echo -e "\n\033[1;33m-->\033[1;34m Updating AUR packages with paru..\033[0m"
    sudo -u "#$SUDO_UID" paru -Sua || { echo -e "\033[1;31m\nParu exited with error code $?\n\033[0m"; }
  elif [ -x /usr/bin/yay ] && [[ -n "$SUDO_UID" ]]; then
    echo -e "\n\033[1;33m-->\033[1;34m Updating AUR packages with yay..\033[0m"
    sudo -u "#$SUDO_UID" yay -Sua || { echo -e "\033[1;31m\nYay exited with error code $?\n\033[0m"; }
  else
    echo -e "\n\033[1;33m--> UPDATE_AUR specified but no supported AUR helper found ❌\033[0m"
  fi
  $INT
fi

if [[ -v UPDATE_AUR ]] && [[ -v GARUDA_UPDATE_RANI ]] && [[ -v FIREFLY_PACKAGE_MANAGER ]]; then
  # In this case we must not attempt running the update. There is no way to confirm/review updates as of today.
  echo -e "\n\033[1;33m-->\033[1;34m Skipping AUR update. Running those via Rani is explicitly not supported. Instead, use the terminal and review updates manually.\033[0m"
fi

/usr/lib/garuda/garuda-update/update-helper-scripts end-routines

echo -e "\n\033[1;32mSystem updated! 🐧\n\033[0m"
show_changelog && HAD_CHANGELOG=1 || true

launch_health

if [ -v HAD_CHANGELOG ]; then
  echo -e "\033[1;32m\nDo not forget to read the update notices above the garuda-health output!\033[0m"
fi
