#!/bin/sh

# do some basic sanity checks

# shellcheck source=conf/default.conf
. "${0%/*}/../conf/default.conf"

usage() {
  >&2 echo ''
  >&2 echo 'sanity-check [options] [checks]: check sanity of build master'
  >&2 echo ''
  >&2 echo 'possible options:'
  >&2 echo '  -h|--help:             Show this help and exit.'
  >&2 echo '  -q|--quiet:            Only print errors found.'
  >&2 echo '  -r|--really-quiet:     Do not print anything.'
  [ -z "$1" ] && exit 1 || exit "$1"
}

i_am_insane() {
  echo 'build master is insane' > "${work_dir}/build-master-sanity"
  exit 1
}

eval set -- "$(
  getopt -o hqr \
    --long help \
    --long quiet \
    --long really-quiet \
    -n "$(basename "$0")" -- "$@" || \
  echo usage
)"

silence=0
repos="${standalone_package_repositories} ${stable_package_repositories} ${testing_package_repositories} ${staging_package_repositories}"

while true
do
  case "$1" in
    -h|--help)
      usage 0
    ;;
    -q|--quiet)
      silence=1
    ;;
    -r|--really-quiet)
      silence=2
    ;;
    --)
      shift
      break
    ;;
    *)
      >&2 echo 'Whoops, forgot to implement option "'"$1"'" internally.'
      exit 42
    ;;
  esac
  shift
done

tmp_dir=$(mktemp -d)
trap 'rm -rf --one-file-system "${tmp_dir}"' EXIT

if [ $# -eq 0 ]; then
  set -- build-list repos package-database state-files
fi

while [ $# -gt 0 ]; do

  case "$1" in

    build-list)

      [ ${silence} -gt 0 ] || \
        >&2 printf 'checking build-list ...'

      errors=$(
        grep -vn '^\S\+ [0-9a-f]\{40\} [0-9a-f]\{40\} \S\+$\|^break_loops$' "${work_dir}/build-list"
      ) || true
      if [ -n "${errors}" ]; then
        if [ ${silence} -le 1 ]; then
          echo
          echo "The following build orders are wrongly formatted:"
          echo "${errors}"
        fi
        i_am_insane
      fi

      errors=$(
        grep -vxF 'break_loops' "${work_dir}/build-list" | \
          awk '{print $1}' | \
          sort | \
          uniq -d
      )
      if [ -n "${errors}" ]; then
        if [ ${silence} -le 1 ]; then
          echo
          echo "The following packages have duplicate build orders:"
          echo "${errors}"
        fi
        i_am_insane
      fi

      errors=$(
        (
          grep -vxF 'break_loops' "${work_dir}/build-list" | \
            awk '{print $1}'
          cat "${work_dir}/deletion-list"
        ) | \
          sort | \
          uniq -d
      )
      if [ -n "${errors}" ]; then
        if [ ${silence} -le 1 ]; then
          echo
          echo "The following packages appear on the build- and deletion-list:"
          echo "${errors}"
        fi
        i_am_insane
      fi

      [ ${silence} -gt 0 ] || \
        >&2 echo ' passed.'

    ;;

    repos)

      [ ${silence} -gt 0 ] || \
        >&2 printf 'checking repos on master mirror ...'

      errors=$(
        (
          # shellcheck disable=SC2086
          printf 'expected %s\n' ${repos}
          ls_master_mirror 'i686' | \
            sed 's|^|found |'
        ) | \
          sort -k2 | \
          uniq -uf1
      )
      if [ -n "${errors}" ]; then
        if [ ${silence} -le 1 ]; then
          echo
          echo "The following repos are missing or obsolete on the mirror:"
          echo "${errors}"
        fi
        i_am_insane
      fi

      [ ${silence} -gt 0 ] || \
        >&2 echo ' passed.'

    ;;

    package-database)

      for repo in ${repos}; do

        [ ${silence} -gt 0 ] || \
          >&2 printf 'checking consistency of repository "%s" on the master mirror ...' "${repo}"

        packages=$(
          ls_master_mirror "i686/${repo}" | \
            grep '\.pkg\.tar\.xz\(\.sig\)\?$'
        ) || true

        errors=$(
          echo "${packages}" | \
            grep '\S' | \
            sed '
              s|^\(.*\.pkg\.tar\.xz\)$|package \1|
              s|^\(.*\.pkg\.tar\.xz\)\.sig$|signature \1|
            ' | \
            sort -k2 | \
            uniq -cf1 | \
            grep -v '^\s*2\s' | \
            awk '{print $2 " " $3}'
        ) || true
        if [ -n "${errors}" ]; then
          if [ ${silence} -le 1 ]; then
            echo
            echo "The following packages in ${repo} are missing a signature or vice versa:"
            echo "'${errors}'"
          fi
          i_am_insane
        fi

        ${master_mirror_rsync_command} \
          "${master_mirror_rsync_directory}/i686/${repo}/${repo}.db.tar.gz" \
          "${master_mirror_rsync_directory}/i686/${repo}/${repo}.files.tar.gz" \
          "${tmp_dir}/"

        errors=$(
          (
            tar -tzf "${tmp_dir}/${repo}.db.tar.gz" | \
              grep '/$' | \
              sed '
                s|/$||
                s|^|in_database |
              '
            echo "${packages}" | \
              grep '\S' | \
              sed '
                s|-[^-]\+$||
                s|^|in_repository |
              ' | \
              sort -u
          ) | \
            sort -k2 | \
            uniq -uf1
        )
        if [ -n "${errors}" ]; then
          if [ ${silence} -le 1 ]; then
            echo
            echo "The following packages in ${repo} are missing from the database or vice versa:"
            echo "${errors}"
          fi
          i_am_insane
        fi

        errors=$(
          (
            tar -tzf "${tmp_dir}/${repo}.files.tar.gz" | \
              grep '/$' | \
              sed '
                s|/$||
                s|^|in_database |
              '
            echo "${packages}" | \
              grep '\S' | \
              sed '
                s|-[^-]\+$||
                s|^|in_repository |
              ' | \
              sort -u
          ) | \
            sort -k2 | \
            uniq -uf1
        )
        if [ -n "${errors}" ]; then
          if [ ${silence} -le 1 ]; then
            echo
            echo "The following packages in ${repo} are missing from the file-database or vice versa:"
            echo "${errors}"
          fi
          i_am_insane
        fi

        rm -rf --one-file-system "${tmp_dir:?}/"*

        [ ${silence} -gt 0 ] || \
          >&2 echo ' passed.'

      done

    ;;

    state-files)

      for status in 'staging:done' 'testing:testing'; do

        [ ${silence} -gt 0 ] || \
          >&2 printf 'checking state-files of "%s" ...' "${status%:*}"

        errors=$(
          (
            find "${work_dir}/package-states" -name "*.${status#*:}" -exec \
              cat {} \; | \
              sed 's|^|package-state-file |'
            ls_master_mirror 'i686' | \
              grep "${status%:*}\$" | \
              while read -r repo; do
                ls_master_mirror "i686/${repo}"
              done | \
              grep '\.pkg\.tar\.xz$' | \
              sed 's|^|package-file |'
          ) | \
            sort -k2 | \
            uniq -cf1 | \
            grep -v '^\s*2\s' | \
            awk '{print $2 " " $3}'
        )
        if [ -n "${errors}" ]; then
          if [ ${silence} -le 1 ]; then
            echo
            echo "The following ${status%:*} packages do not have state files or vice versa:"
            echo "${errors}"
          fi
          i_am_insane
        fi

        [ ${silence} -gt 0 ] || \
          >&2 echo ' passed.'

      done

    ;;

    *)

      [ ${silence} -gt 1 ] || \
        >&2 printf 'unknown sanity-check "%s".\n' "$1"
      exit 2

    ;;

  esac

  shift

done

rm -f "${work_dir}/build-master-sanity"