set -xu

if test "${SCRATCH:-}" = ""
then
  export SCRATCH=/tmp
else
  export SCRATCH
fi

export BUPSTASH_REPOSITORY="$SCRATCH/thrash_repo"
export BUPSTASH_KEY="$SCRATCH/thrash.key"
export BUPSTASH_QUERY_CACHE="$SCRATCH/thrash.qcache"
export MINIO_ACCESS_KEY="thrash_access"
export MINIO_SECRET_KEY="thrash_secret"
export N_WORKERS=$(nproc)

trap "trap - SIGTERM ; kill -9 -- -$$" SIGINT SIGTERM EXIT

rm -rf "$BUPSTASH_REPOSITORY"
rm -f "$BUPSTASH_KEY"

bupstash new-key -o "$BUPSTASH_KEY"
bupstash init --storage \
"{\"ExternalStore\":{\"path\":\"s3://thrash_access:thrash_secret@thrashbucket?secure=false&endpoint=localhost%3A9000\",\"socket_path\":\"$SCRATCH/bupstash-s3-storage.sock\"}}"
rm -f "$SCRATCH/thrash.summary"
sqlite3 "$SCRATCH/thrash.summary" "create table thrash_results(name, count, unique(name));"

inc_result () {
  sqlite3 "$SCRATCH/thrash.summary" \
    "PRAGMA busy_timeout = 10000;
     begin immediate;
     insert into thrash_results(name, count) values('$1', 0)
     on conflict(name) do update set count=count+1 where name = '$1'; commit;" > /dev/null
}

thrash_worker () {
  for i in $(seq 50)
  do
    expected=$(uuidgen)
    
    id=$(bupstash put -q -e --no-send-log thrash_test=yes :: echo $expected)
    
    if test "$?" = 0
    then
      inc_result "put-ok"

      actual="$(bupstash get -q id=$id)"
      if test "$?" = 0
      then
        inc_result "get-ok"
        if test "$expected" != "$actual"
        then
          inc_result "get-corrupt"
        fi
      else
        inc_result "get-fail"
      fi

      bupstash rm -q id="$id" >&2
      if test "$?" = 0
      then
        inc_result "rm-ok"
      else
        inc_result "rm-fail"
      fi
    else
      inc_result "put-fail"
    fi

    bupstash recover-removed -q >&2
    if test "$?" = 0
    then
      inc_result "recover-removed-ok"
    else
      inc_result "recover-removed-fail"
    fi

    bupstash gc -q >&2
    if test "$?" = 0
    then
      inc_result "gc-ok"
    else
      inc_result "gc-fail"
    fi
  done

  rm -f "$SCRATCH/want_chaos"
}

bupstash_serve_chaos_worker () {
  while test -f "$SCRATCH/want_chaos"
  do
    kill -9 $(ps -aux  | grep 'bupstash serve' | grep -v "grep" | awk '{print $2}' | shuf | head -n $(($RANDOM/$N_WORKERS)))
    sleep 1
  done
}

bupstash_s3_plugin_chaos_worker () {
  while test -f "$SCRATCH/want_chaos"
  do
    killall -s SIGKILL 'bupstash-s3-storage'
    sleep 1
  done
}

s3_plugin_supervisor () {
  cd "$SCRATCH"
  while true
  do
    rm -f "./bupstash-s3-storage.sock"
    bupstash-s3-storage -quiescent-period 10ms >&2
  done
}

minio server "$SCRATCH/miniodata" >&2 &
minio_pid="$!"
s3_plugin_supervisor &
s3_plugin_supervisor_pid="$!"
# give both some time to start.
sleep 1

# Configure the test minio instance.
rm -rf "$SCRATCH/mc"
mc config host add thrashminio http://127.0.0.1:9000 thrash_access thrash_secret >&2 

# Outer loop is to control the size of the gc set.
for i in $(seq 50)
do

  bupstash rm --allow-many thrash_test=yes >&2
  bupstash gc >&2

  background_workers=()
  # At least enough workers so the scheduler hopefully
  # interleaves them in interesting ways.
  for j in $(seq $N_WORKERS)
  do
    thrash_worker &
    background_workers+=($!)
  done

  touch "$SCRATCH/want_chaos"
  bupstash_serve_chaos_worker &
  background_workers+=($!)
  bupstash_s3_plugin_chaos_worker &
  background_workers+=($!)

  wait ${background_workers[@]}

  for id in $(bupstash list -q --format=jsonl | jq -r .id)
  do
    bupstash get -q id=$id > /dev/null
    if test "$?" != 0
    then
      inc_result "get-corrupt"
    fi
  done

  if sqlite3 "$SCRATCH/thrash.summary" 'select * from thrash_results;' | grep -q 'get\-corrupt'
  then
    echo "invariant check failed, 'get' should never return a corrupt result"
    exit 1
  fi

done

if test $(sqlite3 "$SCRATCH"/thrash.summary "select count from thrash_results where name='put-ok';") = ""
then
  echo "at least one 'put' operation must succeed for the test to pass."
  exit 1
fi

if test $(sqlite3 "$SCRATCH"/thrash.summary "select count from thrash_results where name='gc-ok';") = ""
then
  echo "at least one 'gc' operation must succeed for the test to pass."
  exit 1
fi

# Cleanup any remains
kill $s3_plugin_supervisor_pid
kill $minio_pid

# XXX hacky, but cleanup any bupstash-s3-storage instances that might have been restarted by the supervisor.
sleep 0.5
killall bupstash-s3-storage
wait

trap - EXIT

set +x


echo "test results..."
sqlite3 "$SCRATCH/thrash.summary" 'select * from thrash_results order by name;'
echo "test passed"

