#!/usr/bin/env uv run bash

# ┌───────────────────────────────────────────────────────────────┐
# │ Contents of tests.sh                                          │
# ├───────────────────────────────────────────────────────────────┘
# │
# ├──┐VARIABLES
# │  ├── MAIN
# │  └── DERIVED
# ├──┐FUNCTIONS
# │  ├── PREPARATION
# │  ├──┐RUSTICLONE BACKUP
# │  │  ├── SEQUENTIAL
# │  │  ├── PARALLEL
# │  │  └── BACKGROUND
# │  ├──┐DISASTER SIMULATION
# │  │  ├── LOSING SOURCE FILES
# │  │  ├── LOSING CACHE
# │  │  ├── LOSING LOCAL BACKUP
# │  │  └── LOSING REMOTE BACKUP
# │  ├──┐RUSTICLONE RESTORE
# │  │  ├── SEQUENTIAL
# │  │  ├── PARALLEL
# │  │  └── BACKGROUND
# │  ├── RESULT
# │  └── MAIN
# ├── COMMANDS
# │
# └───────────────────────────────────────────────────────────────

set -euo pipefail

# ################################################################ VARIABLES
# ################################ MAIN

# must either be ~/.config/rustic or /etc/rustic
RUSTIC_PROFILES_DIR="$HOME/.config/rustic"

# can be any folder
RUSTICLONE_TEST_DIR="$HOME/.cache/rusticlone-tests"

# ################################ DERIVED

mapfile -d '' profile1Content << CONTENT
[global]
use-profiles = ["common"]

[repository]
repository = "$RUSTICLONE_TEST_DIR/local/Documents"
cache-dir = "$RUSTICLONE_TEST_DIR/cache"
password = "XXXXXX"

[[backup.snapshots]]
sources = ["$RUSTICLONE_TEST_DIR/source/docs"]

[forget]
prune = true
keep-last = 1

[global.env]
RCLONE_CONFIG = "$RUSTIC_PROFILES_DIR/rclone-decrypted.conf"
CONTENT

mapfile -d '' profile2Content << CONTENT
[repository]
repository = "$RUSTICLONE_TEST_DIR/local/Pictures"
cache-dir = "$RUSTICLONE_TEST_DIR/cache"
password = "XXXXXX"

[[backup.snapshots]]
sources = ["$RUSTICLONE_TEST_DIR/source/pics"]

[[backup.snapshots]]
sources = ["$RUSTICLONE_TEST_DIR/source/photos"]

[forget]
keep-daily = 10

[global.env]
RCLONE_CONFIG = "$RUSTIC_PROFILES_DIR/rclone-encrypted.conf"
RCLONE_CONFIG_PASS = "YYYYYY"
CONTENT

mapfile -d '' profile3Content << CONTENT
[repository]
repository = "$RUSTICLONE_TEST_DIR/local/Passwords"
cache-dir = "$RUSTICLONE_TEST_DIR/cache"
password = "XXXXXX"

[[backup.snapshots]]
sources = [
  "$RUSTICLONE_TEST_DIR/source/passwords.kdbx",
  "$RUSTICLONE_TEST_DIR/source/secrets"
]

[global.env]
RCLONE_CONFIG = "$RUSTIC_PROFILES_DIR/rclone-encrypted.conf"
RCLONE_PASSWORD_COMMAND = "python -c \"print('YYYYYY')\""
CONTENT

mapfile -d '' profileCommonContent << CONTENT
[global]
log-level = "debug"
log-file = "$RUSTICLONE_TEST_DIR/logs/rusticlone.log"
CONTENT

mapfile -d '' rcloneConfContentDecrypted << CONTENT
[gdrive]
type = local
CONTENT

mapfile -d '' rcloneConfContentEncrypted << CONTENT
# Encrypted rclone configuration File

RCLONE_ENCRYPT_V0:
LDDUg4mDyUxDwMtntnCaiUN+o9SexiohA8Y74ZYJmPD9KD8UjVtH9XYCL+3A6OGR7msabjvu0Gj2W8JRande
CONTENT

GOOD_APPRISE_URL="form://example.org"
BAD_APPRISE_URL="moz://a"

# ################################################################ FUNCTIONS
# ################################ PREPARATION

stopwatch_begin(){
 datebegin="$EPOCHSECONDS"
 export datebegin
}

stopwatch_end(){
 dateend="$EPOCHSECONDS"
 datediff="$((dateend-datebegin))"
 runtime="$(date -ud "@$datediff" -u +'%-Mm %-Ss')"
 echo "[OK] Tests completed succesfully in $runtime"
}

check_workdir(){
 if [[ "tests/tests.sh" != *$1 ]]; then
  echo "[KO] Please run this script from the main folder as 'tests/test.sh'"
  exit 1
 fi
}

logecho(){
 if [ "${2+isSet}" ]; then
  logFile="$2"
 else
  logFile="$RUSTICLONE_TEST_DIR/logs/rusticlone.log"
 fi
 echo " "
 echo "$1"
 echo " "
 echo " "  >> "$logFile"
 echo "$1" >> "$logFile"
}

print_space(){
 echo " "
 echo " "
}

print_warning(){
 echo "[!!] WARNING: this script will destroy the contents of \"$RUSTIC_PROFILES_DIR\" and \"$RUSTICLONE_TEST_DIR\""
 echo "[!!] Required programs: bash, coreutils, python-coverage, rustic, rclone"
 sleep 5
}

check_dependencies(){
  for dep in b2sum coverage python rustic rclone; do
    if ! command -v "$dep" &> /dev/null; then
      echo "[KO] $dep could not be found"
      exit 1
    fi
  done
}

print_cleanup(){
 echo "[OK] Test completed, feel free to read test coverage and remove \"$RUSTIC_PROFILES_DIR\" and \"$RUSTICLONE_TEST_DIR\""
}

remove_profiles_dir(){
 if [[ -d "$RUSTIC_PROFILES_DIR" ]]; then
  rm -r "$RUSTIC_PROFILES_DIR"
 fi
}

create_dirs(){
 echo "[OK] Creating directories"
 remove_profiles_dir
 if [[ -d "$RUSTICLONE_TEST_DIR" ]]; then
  rm -r "$RUSTICLONE_TEST_DIR"
 fi
 mkdir -p "$RUSTIC_PROFILES_DIR" "$RUSTICLONE_TEST_DIR"/{check,source/docs,source/pics,source/photos/deeply/nested,source/secrets,logs}
}

create_confs(){
 echo "[OK] Creating configurations"
 profile1Conf="$RUSTIC_PROFILES_DIR/Documents-test.toml"
 profile2Conf="$RUSTIC_PROFILES_DIR/Pictures-test.toml"
 profile3Conf="$RUSTIC_PROFILES_DIR/Passwords-test.toml"
 profileCommonConf="$RUSTIC_PROFILES_DIR/common.toml"
 rcloneConfDecrypted="$RUSTIC_PROFILES_DIR/rclone-decrypted.conf"
 rcloneConfEncrypted="$RUSTIC_PROFILES_DIR/rclone-encrypted.conf"
 echo "${profile1Content[0]}" > "$profile1Conf"
 echo "${profile2Content[0]}" > "$profile2Conf"
 echo "${profile3Content[0]}" > "$profile3Conf"
 echo "${profileCommonContent[0]}" > "$profileCommonConf"
 echo "${profileCommonContent[0]}" >> "$profile2Conf"
 echo "${profileCommonContent[0]}" >> "$profile3Conf"
 echo "${rcloneConfContentDecrypted[0]}" > "$rcloneConfDecrypted"
 echo "${rcloneConfContentEncrypted[0]}" > "$rcloneConfEncrypted"
}

create_files(){
 # 10MB each
 echo "[OK] Creating files"
 head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/docs/important.pdf"
 head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/docs/veryimportant.pdf"
 head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/docs/notsoimportant.docx"
 head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/pics/screenshot.png"
 head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/pics/opengraph.webp"
 head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/pics/funny.gif"
 head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/photos/photo.jpeg"
 head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/photos/deeply/nested/memory.avif"
 sameFile="$(head -c 10000000 /dev/urandom | base64)"
 echo "$sameFile" > "$RUSTICLONE_TEST_DIR/source/passwords.kdbx"
 echo "$sameFile" "almost" > "$RUSTICLONE_TEST_DIR/source/secrets/env"
 chmod 0600 "$RUSTICLONE_TEST_DIR/source/passwords.kdbx"
}

create_new_files(){
 # 10MB each
 echo "[OK] Creating files"
 head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/docs/important2.pdf"
 head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/docs/veryimportant2.pdf"
 head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/docs/notsoimportant2.docx"
 head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/pics/screenshot2.png"
 head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/pics/opengraph2.webp"
 head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/pics/funny2.gif"
 head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/photos/photo2.jpeg"
 head -c 10000000 /dev/urandom > "$RUSTICLONE_TEST_DIR/source/photos/deeply/nested/memory2.avif"
}

create_check_source(){
 echo "[OK] Creating checksums for source files"
 find "$RUSTICLONE_TEST_DIR/source" -type f -exec b2sum {} \; > "$RUSTICLONE_TEST_DIR/check/source.txt"
}

wait_background(){
 while wait -n; do : ; done;
}

# ################################ RUSTICLONE BACKUP
# ################ SEQUENTIAL

rusticlone_backup(){
 logecho "[OK] Backing up with Rusticlone"
 coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" -a "$GOOD_APPRISE_URL" backup
}

rusticlone_archive(){
 logecho "[OK] Archiving with Rusticlone"
 coverage run --append --module rusticlone.cli archive
}

rusticlone_upload(){
 logecho "[OK] Uploading with Rusticlone"
 coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" upload
}

rusticlone_backup_flags(){
 touch "$RUSTICLONE_TEST_DIR/logs/log-specified-in-args.log"
 logecho "[OK] Backing up from Rusticlone" "$RUSTICLONE_TEST_DIR/logs/log-specified-in-args.log"
 coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" -P "Pictures-test" --log-file "$RUSTICLONE_TEST_DIR/logs/log-specified-in-args.log" backup
 logecho "[OK] Backing up from Rusticlone"
 coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" -P "Documents-test" backup
 coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" -P "Passwords-test" -a "$BAD_APPRISE_URL" backup
}

# ################ PARALLEL

rusticlone_backup_parallel(){
 echo "[OK] Backing up with Rusticlone using parallel mode"
 coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" --parallel backup
}

rusticlone_archive_parallel(){
 echo "[OK] Archiving with Rusticlone using parallel mode"
 coverage run --append --module rusticlone.cli --parallel archive
}

rusticlone_upload_parallel(){
 echo "[OK] Uploading with Rusticlone using parallel mode"
 coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" --parallel upload
}

# ################ BACKGROUND

rusticlone_backup_background(){
 echo "[OK] Backing up with Rusticlone in background"
 coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" --parallel backup >/dev/null &
}

rusticlone_archive_background(){
 echo "[OK] Archiving with Rusticlone in background"
 coverage run --append --module rusticlone.cli archive >/dev/null &
}

rusticlone_upload_background(){
 echo "[OK] Uploading with Rusticlone in background"
 coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" upload >/dev/null &
}

# ################################ DISASTER SIMULATION
# ################ LOSING SOURCE FILES

destroy_source1(){
 echo "[OK] Destroying documents"
 rm -rf "$RUSTICLONE_TEST_DIR/source/docs"
}

destroy_source2(){
 echo "[OK] Destroying pictures"
 rm -rf "$RUSTICLONE_TEST_DIR/source/pics"
}

destroy_source3(){
 echo "[OK] Destroying passwords"
 rm -rf "$RUSTICLONE_TEST_DIR/source/passwords.kdbx"
}

destroy_source12(){
 echo "[OK] Destroying documents and pictures"
 rm -rf "$RUSTICLONE_TEST_DIR/source"
}

# ################ LOSING CACHE

destroy_cache(){
 echo "[OK] Destroying cache"
 rm -rf "$RUSTICLONE_TEST_DIR/cache"
}

# ################ LOSING LOCAL BACKUP

destroy_local1(){
 echo "[OK] Destroying local documents backup"
 rm -rf "$RUSTICLONE_TEST_DIR/local/Documents"
}

destroy_local2(){
 echo "[OK] Destroying local pictures backup"
 rm -rf "$RUSTICLONE_TEST_DIR/local/Pictures"
}

destroy_local12(){
 echo "[OK] Destroying local documents and pictures backup"
 rm -rf "$RUSTICLONE_TEST_DIR/local"
}

# ################ LOSING REMOTE BACKUP

destroy_remote1(){
 echo "[OK] Destroying remote documents backup"
 rm -rf "$RUSTICLONE_TEST_DIR/remote/Documents"
}

destroy_remote2(){
 echo "[OK] Destroying remote pictures backup"
 rm -rf "$RUSTICLONE_TEST_DIR/remote/Pictures"
}

destroy_remote12(){
 echo "[OK] Destroying remote documents and pictures backups"
 rm -rf "$RUSTICLONE_TEST_DIR/remote"
}

# ################################ RUSTICLONE RESTORE
# ################ SEQUENTIAL

rusticlone_restore(){
 logecho "[OK] Restoring from Rusticlone"
 coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" restore
}

rusticlone_restore_flags(){
 touch "$RUSTICLONE_TEST_DIR/logs/log-specified-in-args.log"4
 logecho "[OK] Restoring from Rusticlone" "$RUSTICLONE_TEST_DIR/logs/log-specified-in-args.log"
 coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" -P "Documents-test" --log-file "$RUSTICLONE_TEST_DIR/logs/log-specified-in-args.log" restore
 logecho "[OK] Restoring from Rusticlone"
 coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" -P "Passwords-test" restore
 coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" -P "Pictures-test" restore
}

rusticlone_download(){
 logecho "[OK] Downloading from Rusticlone"
 coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" download
}

rusticlone_extract(){
 logecho "[OK] Extracting from Rusticlone"
 coverage run --append --module rusticlone.cli extract
}

# ################ PARALLEL

rusticlone_restore_parallel(){
 echo "[OK] Restoring with Rusticlone using parallel mode"
 coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" --parallel restore
}

rusticlone_download_parallel(){
 echo "[OK] Downloading with Rusticlone using parallel mode"
 coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" --parallel download
}

rusticlone_extract_parallel(){
 echo "[OK] Extracting with Rusticlone using parallel mode"
 coverage run --append --module rusticlone.cli --parallel extract
}

# ################ BACKGROUND

rusticlone_restore_background(){
 echo "[OK] Restoring with Rusticlone in background"
 coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" --parallel restore >/dev/null &
}

rusticlone_download_background(){
 echo "[OK] Downloading with Rusticlone in background"
 coverage run --append --module rusticlone.cli --remote "gdrive:/$RUSTICLONE_TEST_DIR/remote" download >/dev/null &
}

rusticlone_extract_background(){
 echo "[OK] Extracting with Rusticlone in background"
 coverage run --append --module rusticlone.cli --parallel extract >/dev/null &
}

# ################################ RESULT

check_source(){
 echo "[OK] Checking sums for source files"
 b2sum --check "$RUSTICLONE_TEST_DIR/check/source.txt" || exit 1
}

create_coverage(){
 coverage html
 coverage xml
 coverage report
 rm -rf "tests/coverage"
 mv "htmlcov" "$RUSTICLONE_TEST_DIR/coverage"
 mv "coverage.xml" "$RUSTICLONE_TEST_DIR/coverage"
}

create_badge(){
 genbadge coverage -i "$RUSTICLONE_TEST_DIR/coverage/coverage.xml" -o "images/coverage.svg"
}

check_coverage(){
 echo " "
 echo "[OK] Read the coverage report in detail by running:"
 echo " "
 echo "    firefox \"$RUSTICLONE_TEST_DIR/coverage/index.html\""
 echo " "
}

# ################################ MAIN

main(){
 # preparation
 check_workdir "$0"
 print_warning
 check_dependencies
 stopwatch_begin
 create_dirs
 create_confs
 create_files
 create_check_source
 
 # backup
 rusticlone_backup
 print_space
 destroy_local1
 destroy_remote2
 print_space
 rusticlone_backup_parallel
 print_space
 destroy_remote1
 destroy_local2
 print_space
 rusticlone_backup_background
 rusticlone_backup_background
 rusticlone_backup_parallel
 rusticlone_upload_background
 rusticlone_archive_background
 rusticlone_upload_background
 rusticlone_archive_background
 rusticlone_archive
 rusticlone_backup_background
 wait_background
 print_space
 destroy_cache
 print_space
 rusticlone_upload_parallel
 print_space
 destroy_remote2
 destroy_cache
 print_space
 rusticlone_archive_parallel
 rusticlone_upload
 print_space
 destroy_source12
 destroy_source3
 destroy_local2
 print_space

 # restore
 rusticlone_restore
 print_space
 destroy_source1
 destroy_local2
 print_space
 rusticlone_restore_background
 rusticlone_restore_parallel
 wait_background
 print_space
 check_source
 destroy_local1
 destroy_source2
 print_space
 rusticlone_restore_background
 rusticlone_download_background
 print_space
 rusticlone_download_parallel
 rusticlone_archive_parallel
 rusticlone_extract
 rusticlone_extract
 wait_background
 print_space
 check_source
 destroy_cache
 print_space
 rusticlone_download
 rusticlone_extract_background
 rusticlone_extract_parallel
 rusticlone_extract_background
 rusticlone_extract_parallel
 wait_background
 print_space
 check_source
 print_space
 destroy_cache
 destroy_source1
 destroy_local2
 print_space
 rusticlone_restore_background
 print_space
 rusticlone_restore
 wait_background
 print_space
 destroy_source2
 destroy_local1
 print_space
 rusticlone_restore_flags
 print_space

 # further run
 check_source
 create_new_files
 rusticlone_backup
 rusticlone_restore
 check_source
 print_space

 # results
 remove_profiles_dir
 create_coverage
 create_badge
 check_coverage
 stopwatch_end
}

# ################################################################ COMMANDS

main "$@"
