#!/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.'
  >&2 echo '  -w|--webserver:        Generate output suitable for webserver.'
  [ -z "$1" ] && exit 1 || exit "$1"
}

i_am_insane() {
  if [ ! -s "${work_dir}/build-master-sanity" ]; then
    printf '\001ACTION goes insane.\001\n' | \
      irc_say
  fi
  echo 'build master is insane' > \
    "${work_dir}/build-master-sanity"
  echo 'SANITY CHECK FAILED' >> \
    "${tmp_dir}/messages"
  exit 1
}

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

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

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

exec 9> "${sanity_check_lock_file}"
if ! flock -n 9; then
  >&2 echo 'Sanity check skipped, cannot acquire lock.'
  exit
fi

finish() {
  if ${web}; then
    {
      printf '%s\n' \
        '<html>' \
        '<head>' \
        '<title>result of archlinux32 build master'"'"'s sanity check</title>' \
        '</head>' \
        '<body>'
      printf '%s<br>\n' "$(date)"
      sed 's|$|<br>|' "${tmp_dir}/messages"
      printf '%s\n' \
        '</body>' \
        '</html>'
    } > \
      "${webserver_directory}/master-sanity.html"
  else
    cat "${tmp_dir}/messages" >&2
  fi
  rm -rf --one-file-system "${tmp_dir}"
}

tmp_dir=$(mktemp -d 'tmp.sanity-check.XXXXXXXXXX' --tmpdir)
touch "${tmp_dir}/messages"
trap 'finish' EXIT

if [ $# -eq 0 ]; then
  set -- git-repositories build-list mysql repos package-database track-state
fi

while [ $# -gt 0 ]; do

  case "$1" in

    git-repositories)

      [ ${silence} -gt 0 ] || \
        printf 'checking git repositories ...' >> \
        "${tmp_dir}/messages"

      for repo in ${repo_names}; do
        eval 'repo_path="${repo_paths__'"${repo}"'}"'
        repo_revision=$(
          # shellcheck disable=SC2016
          {
            printf 'SELECT `git_repositories`.`head` FROM `git_repositories`'
            printf ' WHERE `git_repositories`.`name`=from_base64("%s");\n' \
              "$(printf '%s' "${repo}" | base64 -w0)"
          } | \
            mysql_run_query
        )
        if ! obj_type=$(git -C "${repo_path}" cat-file -t "${repo_revision}" 2>/dev/null); then
          if [ ${silence} -le 1 ]; then
            printf '\nThe repository %s (%s) does not know the current revision %s.\n' \
              "${repo}" "${repo_path}" "${repo_revision}" >> \
              "${tmp_dir}/messages"
          fi
          i_am_insane
        elif [ "${obj_type}" != 'commit' ]; then
          if [ ${silence} -le 1 ]; then
            printf '\nThe repository %s (%s) knows the current revision %s, but it is not a commit, but a %s.\n' \
              "${repo}" "${repo_path}" "${repo_revision}" "${obj_type}" >> \
              "${tmp_dir}/messages"
          fi
          i_am_insane
        fi
      done

      [ ${silence} -gt 0 ] || \
        echo ' passed.' >> \
        "${tmp_dir}/messages"

    ;;

    build-list)

      [ ${silence} -gt 0 ] || \
        printf 'checking build-list ...' >> \
        "${tmp_dir}/messages"

      errors=$(
        # shellcheck disable=SC2016
        {
          printf 'SELECT `architectures`.`name`,`package_sources`.`pkgbase` FROM `package_sources`'
          mysql_join_package_sources_build_assignments
          mysql_join_build_assignments_architectures
          printf ' WHERE EXISTS('
            printf 'SELECT * FROM `binary_packages`'
            mysql_join_binary_packages_repositories
            printf ' WHERE `repositories`.`name`="build-list"'
            printf ' AND `binary_packages`.`build_assignment`=`build_assignments`.`id`'
          printf ');\n'
        } | \
          mysql_run_query | \
          sort | \
          uniq -d
      )
      if [ -n "${errors}" ]; then
        if [ ${silence} -le 1 ]; then
          printf '\nThe following packages have duplicate build orders:\n%s\n' \
            "${errors}" >> \
            "${tmp_dir}/messages"
        fi
        i_am_insane
      fi

      errors=$(
        # shellcheck disable=SC2016
        {
          printf 'SELECT `a`.`pkgname` FROM `binary_packages` AS `a`'
          mysql_join_binary_packages_repositories 'a' 'a_r'
          printf ' AND `a_r`.`name`="build-list"'
          printf ' JOIN `binary_packages` AS `b` ON `a`.`pkgname`=`b`.`pkgname`'
          mysql_join_binary_packages_repositories 'b' 'b_r'
          printf ' AND `b_r`.`name`="deletion-list";\n'
        } | \
          mysql_run_query
      )
      if [ -n "${errors}" ]; then
        if [ ${silence} -le 1 ]; then
          printf '\nThe following packages appear on the build- and deletion-list:\n%s\n' \
            "${errors}" >> \
            "${tmp_dir}/messages"
        fi
        i_am_insane
      fi

      [ ${silence} -gt 0 ] || \
        echo ' passed.' >> \
        "${tmp_dir}/messages"

    ;;

    repos)

      [ ${silence} -gt 0 ] || \
        printf 'checking repos on master mirror ...' >> \
        "${tmp_dir}/messages"

      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
          printf '\nThe following repos are missing or obsolete on the mirror:\n%s\n' \
            "${errors}" >> \
            "${tmp_dir}/messages"
        fi
        i_am_insane
      fi

      [ ${silence} -gt 0 ] || \
        echo ' passed.' >> \
        "${tmp_dir}/messages"

    ;;

    package-database)

      for repo in ${repos}; do

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

        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
            printf '\nThe following packages in %s are missing a signature or vice versa:\n%s\n' \
              "${repo}" \
              "${errors}" >> \
              "${tmp_dir}/messages"
          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 -Oxzf "${tmp_dir}/${repo}.db.tar.gz" --wildcards '*/desc' 2>/dev/null | \
              sed -n '
                /^%FILENAME%$/ {
                  N
                  s/^.*\n/in_database /
                  p
                }
              '
            echo "${packages}" | \
              sed '
                /\.pkg\.tar\.xz$/ !d
                s/^/in_repository /
              ' | \
              sort -u
          } | \
            sort -k2 | \
            uniq -uf1
        )
        if [ -n "${errors}" ]; then
          if [ ${silence} -le 1 ]; then
            printf '\nThe following packages in %s are missing from the database or vice versa:\n%s\n' \
              "${repo}" \
              "${errors}" >> \
              "${tmp_dir}/messages"
          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
            printf '\nThe following packages in %s are missing from the file-database or vice versa:\n%s\n' \
              "${repo}" \
              "${errors}" >> \
              "${tmp_dir}/messages"
          fi
          i_am_insane
        fi

        find "${tmp_dir:?}" -mindepth 1 \( -not -name 'messages' \) -delete

        [ ${silence} -gt 0 ] || \
          echo ' passed.' >> \
          "${tmp_dir}/messages"

      done

    ;;

    track-state)

      [ ${silence} -gt 0 ] || \
        printf 'checking if all packages are tracked correctly ...' >> \
        "${tmp_dir}/messages"

      errors=$(
        {
          # shellcheck disable=SC2016
          {
            printf 'SELECT "mysql",CONCAT(`repositories`.`name`,"/",'
            mysql_package_name_query
            printf ') FROM `binary_packages`'
            mysql_join_binary_packages_repositories
            printf ' AND `repositories`.`is_on_master_mirror`'
            mysql_join_binary_packages_architectures
          } | \
            mysql_run_query | \
            tr '\t' ' '
          ls_master_mirror 'i686' | \
            while read -r repo; do
              ls_master_mirror "i686/${repo}" | \
                sed '
                  /\.pkg\.tar\.xz$/!d
                  s,^,package-file '"${repo}"'/,
                '
            done
        } | \
          sed 's/\(-[0-9]\+\)\.0\(-[^- ]\+$\)/\1\2/' | \
          sort -k2 | \
          uniq -uf1
      )
      if [ -n "${errors}" ]; then
        if [ ${silence} -le 1 ]; then
          printf '\nThe following packages from the master mirror are not tracked in the database or vice versa:\n%s\n' \
            "${errors}" >> \
            "${tmp_dir}/messages"
        fi
        i_am_insane
      fi

      [ ${silence} -gt 0 ] || \
        echo ' passed.' >> \
        "${tmp_dir}/messages"

    ;;

    mysql)

      [ ${silence} -gt 0 ] || \
        printf 'checking mysql-sanity-check-file ...' >> \
        "${tmp_dir}/messages"

      if [ -s "${webserver_directory}/mysql-sanity.html" ]; then
        if [ ${silence} -le 1 ]; then
          printf '\nThere is something wrong with the database:\n'
          cat "${webserver_directory}/mysql-sanity.html"
        fi >> \
          "${tmp_dir}/messages"
        i_am_insane
      fi

      # hopefully, this gets rid of false positives :-)
      sleep 1

      errors=$(
        find "${work_dir}" -mindepth 1 -maxdepth 1 \
          -name 'tmp.mysql-functions.query.*' \
          -exec sleep 5 \; \
          -exec test -s {} \; \
          -printf '%f\n' \
          -execdir sed 's/^/>> /' '{}' \;
      )
      if [ -n "${errors}" ]; then
        if [ ${silence} -le 1 ]; then
          printf '\nThere are pending mysql queries:\n%s\n' \
            "${errors}"
        fi >> \
          "${tmp_dir}/messages"
        i_am_insane
      fi

      [ ${silence} -gt 0 ] || \
        echo ' passed.' >> \
        "${tmp_dir}/messages"

    ;;

    *)

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

    ;;

  esac

  shift

done

if [ -f "${work_dir}/build-master-sanity" ]; then
  rm "${work_dir}/build-master-sanity"
  printf '\001ACTION resumes sanity.\001\n' | \
    irc_say
fi