#!/bin/sh

set -eu

usage() {
  echo "usage: $0 [OPTIONS] [PACKAGE ...]"
  echo
  echo "When packages are specified, only the named packages are processed"
  echo
  echo "$usage_shared_options"
  echo
}

debci_base_dir=$(readlink -f $(dirname $(readlink -f $0))/..)
. $debci_base_dir/lib/environment.sh
. $debci_base_dir/lib/functions.sh

base_tmp_dir=$(mktemp -d)
cleanup() {
  if [ -d "$base_tmp_dir" ]; then
    rm -rf "$base_tmp_dir"
  fi
}
trap cleanup INT TERM EXIT


# check for new autopkgtest outputs and generate debci metadata for those
# outputs a list of packages that got new results
generate_packages() {
  local pkgs=''
  artifacts_url_base="$(echo "${debci_artifacts_url_base}" | sed "s/{SUITE}/${debci_suite}/")"

  for test_run_exit_code in $(find "$debci_autopkgtest_incoming_dir" -name exitcode | sort); do
    incoming_dir=$(dirname $test_run_exit_code)
    target_dir=$(echo "$incoming_dir" | sed -e 's#/autopkgtest-incoming/#/autopkgtest/#')

    local pkg="$(basename $(dirname "$incoming_dir"))"
    status_dir=$(status_dir_for_package "$pkg")
    mkdir -p "$status_dir"

    mkdir -p "$(dirname "$target_dir")"
    mv "$incoming_dir" "$target_dir"

    generate_package_run "$target_dir" "$status_dir"
    case "$pkgs" in
      *" $pkg")
        ;;
      *)
        pkgs="$pkgs $pkg"
        ;;
    esac
  done
  echo "$pkgs"
}

# generate debci metadata for autopkgtest output $1 into status dir $2
generate_package_run() {
  adtresult="$1"
  status_dir="$2"
  run_id=$(basename "$adtresult")
  pkg=$(basename "$status_dir")
  last_status=$($debci_bin_dir/debci-status "$pkg")
  status_file="${status_dir}/${run_id}.json"
  log_file="${status_dir}/${run_id}.log"
  tmp_dir="${base_tmp_dir}/${debci_suite}/${debci_arch}/${pkg}/${run_id}"
  mkdir -p "$tmp_dir"

  # get run duration
  if [ -f "$adtresult/duration" ]; then
    local duration="$(cat "$adtresult/duration")"
    local last_timestamp="$(date '+%Y-%m-%d %H:%M:%S' -d@$(stat --format=%Y "$adtresult/duration"))"
  else
    # this should never happen
    local duration=0
    local last_timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
  fi

  duration_human=$(seconds_to_human_time "$duration")

  extract_latest_autopkgtest
  generate_package_run_log_file > "$log_file"
  report_status "$pkg" "$status" "($duration_human)"

  generate_package_blame
  generate_package_json "$last_timestamp"

  # update "latest" links
  ln -sf "${run_id}.log" "${status_dir}/latest.log"
  ln -sf "${run_id}.json" "${status_dir}/latest.json"
  rm -f "${status_dir}/latest-autopkgtest"

  # generate relative symlinks for relocatability
  if [ -e $adtresult/log.gz ]; then
      ln -s "../../../../..${adtresult#$debci_data_basedir}/log.gz" \
        "${status_dir}/${run_id}.autopkgtest.log.gz"
  fi
  ln -s "../../../../..${adtresult#$debci_data_basedir}" \
    "${status_dir}/latest-autopkgtest"

  compress_artifacts "$adtresult"
}

extract_latest_autopkgtest() {
  mkdir -p ${tmp_dir}/latest-autopkgtest
  local latest_artifacts="${status_dir}/latest-autopkgtest/artifacts.tar.gz"
  if [ -f "$latest_artifacts" ]; then
    tar -xaf "$latest_artifacts" -C "${tmp_dir}/latest-autopkgtest"
  fi
}

# write package test log to stdout (gets redirected to $log_file)
generate_package_run_log_file()
{
  # platform information
  banner "Platform information"
  echo "Package versions:"
  dpkg-query --show debci autopkgtest autodep8 2>/dev/null | column -t | indent
  echo "Backend: $debci_backend"

  # reason for run
  if [ -s "${status_dir}/reason.txt" ]; then
    banner "Triggers for test run"
    cat "${status_dir}/reason.txt"
    rm "${status_dir}/reason.txt"
  fi

  # package versions
  generate_package_run_dependencies
  generate_package_run_testbed_packages

  # result
  exitcode_to_statusmsg
  banner "Result"
  echo "∙ Status: $status ($message)"
  echo "∙ Duration: ${duration_human}"
}

# set $status and $message from $adtresult's exit code
exitcode_to_statusmsg()
{
  code=$(cat "$adtresult/exitcode")
  case "$code" in
    0)
      status=pass
      message='All tests passed'
      ;;
    2)
      status=pass
      message='Tests passed, but at least one test skipped'
      ;;
    4)
      status=fail
      message='Tests failed'
      ;;
    6)
      status=fail
      message='Tests failed, and at least one test skipped'
      ;;
    8)
      status=fail
      message='No tests in this package'
      ;;
    12)
      status=fail
      message='Erroneous package'
      ;;
    16)
      status=tmpfail
      message='Could not run tests due to a temporary testbed failure'
      ;;
    *)
      status=tmpfail
      message="Unexpected adt-run exit code $code"
      ;;
  esac
}

# part of generate_package_run_log_file, show packages and differences in
# testbed base system
generate_package_run_testbed_packages() {
  if [ ! -e "$adtresult/testbed-packages" ]; then
    return 0
  fi

  if [ -f "${tmp_dir}/latest-autopkgtest/testbed-packages" ]; then
    if ! diff -u \
      --label previous-run/testbed-packages "${tmp_dir}/latest-autopkgtest/testbed-packages" \
      --label current-run/testbed-packages "${adtresult}/testbed-packages" \
        > "${tmp_dir}/base.diff"; then
      banner "Change in the base system since last run"
      cat "${tmp_dir}/base.diff"
    fi
  fi

  banner "Base system"
  cat "${adtresult}/testbed-packages"
}

# part of generate_package_run_log_file, show packages and differences test
# dependencies
generate_package_run_dependencies() {
  deps=$(cat $adtresult/*t-*-packages 2>/dev/null| sort -u)

  if [ -n "$deps" ]; then
    banner "Test dependencies"
    echo "$deps"
    if [ -d ${tmp_dir}/latest-autopkgtest ]; then
      cat ${tmp_dir}/latest-autopkgtest/*t-*-packages 2>/dev/null | sort -u > $tmp_dir/last_test_packages
      if [ -s "$tmp_dir/last_test_packages" ]; then
        if ! echo "$deps" | diff -u --label last-run/test-packages "$tmp_dir/last_test_packages" --label current-run/test-packages - > "$tmp_dir/test-packages.diff"; then
          banner "Change in test packages for $pkg since last test run"
          cat "$tmp_dir/test-packages.diff"
        fi
      fi
    fi
  fi
}

# compute $blame for current $pkg, from tmp_dir/test-packages.diff
generate_package_blame() {
  blame="$(debci-status --field blame --json "$pkg")"
  if [ "$blame" = 'null' ]; then
    blame='[]'
  fi

  diff="${tmp_dir}/test-packages.diff"
  previous_diff="${status_dir}/test-packages.diff"

  if [ ! -e "$diff" ]; then
    return
  fi

  case "$status" in
    pass)
      blame='[]'
      ;;
    fail)
      case "${last_status}" in
        pass)
          # identify the packages to be blamed
          blame="$(debci-blame "${diff}" "$pkg")"
          ;;
        fail)
          # update versions from the blamed packages, but not include new
          # packages in the blame list. the file pointed to by $previous_diff
          # is guaranteed to exist at this point
          if [ -e "${previous_diff}" ]; then
            blamed_pkgs="$(debci-status --field blame "$pkg" | awk '{print($1)}')"
            if [ -z "$blamed_pkgs" ]; then
              blame='[]'
            else
              combinediff "${previous_diff}" "${diff}" > "${diff}.new"
              mv "${diff}.new" "${diff}"
              blame=$(debci-blame "${diff}" "$pkg" $blamed_pkgs)
            fi
          fi
          ;;
      esac
      ;;
  esac

  if [ -f "${diff}" ]; then
    # record dependency chain diff from now to be used in future runs
    cp "${diff}" "${previous_diff}"
  fi
}

# this reads all previous result json files, but should only run
# once for each package, after which the last_pass_version is cached
# in each successive json and copied forwards
find_last_pass_version() {
  last_pass_version="never"
  last_pass_date="never"
  result_json=$(find "${status_dir}" -name '*.json' \
                -and -not -name latest.json \
                -and -not -name history.json \
                | sort -V)
  for f in ${result_json}; do
    if [ $(jq -r .status ${f}) = 'pass' ]; then
      last_pass_version="$(jq -r .version ${f})"
      last_pass_date="$(jq -r .date ${f})"
    fi
  done
}

# arguments: <timestamp>
generate_package_json() {
  local timestamp="$1"
  # test did not run
  local version="n/a"
  if [ -e "$adtresult/testpkg-version" ]; then
      version=$(cut -f2 -d' ' "$adtresult/testpkg-version")
  fi

  local previous_status="${last_status}"
  if [ "${previous_status}" = 'tmpfail' ]; then
    previous_status=$(debci-status --field previous_status "$pkg")
  fi

  # record the last version and date for which the test passed,
  # to distinguish between packages which have never passed, never
  # passed for the current version, or failed recently
  local last_pass_version=""
  local last_pass_date=""

  if [ "${status}" = 'pass' ]; then
    last_pass_version="${version}"
    last_pass_date="${timestamp}"
  elif [ $(debci-status --field last_pass_version "$pkg") != 'unknown' ]; then
    last_pass_version="$(jq -r .last_pass_version ${status_dir}/latest.json)"
    last_pass_date="$(jq -r .last_pass_date ${status_dir}/latest.json)"
  else
    find_last_pass_version
  fi

  if [ -n "$artifacts_url_base" ]; then
    extra_fields=",
  \"log_url\": \"$artifacts_url_base/${target_dir#${debci_data_basedir}/autopkgtest/}/log.gz\",
  \"artifacts_url\": \"$artifacts_url_base/${target_dir#${debci_data_basedir}/autopkgtest/}/artifacts.tar.gz\""
  else
    extra_fields=''
  fi
  # latest entry
  cat > "${status_file}" <<EOF
{
  "run_id": "${run_id}",
  "package": "${pkg}",
  "version": "${version}",
  "date": "${timestamp}",
  "status": "${status}",
  "blame": $blame,
  "previous_status": "${previous_status}",
  "duration_seconds": "${duration}",
  "duration_human": "${duration_human}",
  "message": "${message}",
  "last_pass_version": "${last_pass_version}",
  "last_pass_date": "${last_pass_date}"${extra_fields}
}
EOF

  # TODO cleanup old entries (?)

  # history
  history_file="$tmp_dir/history.json"
  echo '[' > "$history_file"
  sep=''
  entries=$(
    find "$status_dir" -name '*.json' \
      -and -not -name latest.json \
      -and -not -name history.json \
      | sort -Vr
  )
  for entry in $entries; do
    if [ -n "$sep" ]; then
      echo "$sep" >> "$history_file"
    fi
    sep=,
    cat $entry >> "$history_file"
  done
  echo ']' >> "$history_file"
  cp "$history_file" "$status_dir/history.json"
}

generate_status_entry() {
  pass=$(grep -l '"status":\s*"pass",' ${debci_packages_dir}/*/*/latest.json | wc -l)
  fail=$(grep -l '"status":\s*"fail",' ${debci_packages_dir}/*/*/latest.json | wc -l)
  tmpfail=$(grep -l '"status":\s*"tmpfail",' ${debci_packages_dir}/*/*/latest.json | wc -l)
  total=$(($pass + $fail + $tmpfail))
  date="$(date +%Y-%m-%dT%H:%M:%S)"
  mkdir -p "${debci_status_dir}"
  cat > "${debci_status_dir}/${date}.json" <<EOF
{
  "date": "$date",
  "pass": $pass,
  "fail": $fail,
  "tmpfail": $tmpfail,
  "total": $total
}
EOF
  ln -sf "${date}.json" "${debci_status_dir}/status.json"
}

compress_artifacts() {
  local resultdir="$1"
  (
    cd $resultdir
    tar -caf artifacts.tar.gz --exclude=log.gz *
    tar -taf artifacts.tar.gz | xargs rm -rf
  )
}

generate_history() {
  debci-generate-history "${debci_status_dir}" "${debci_status_dir}/history.json"
}

generate_packages_status() {
  $debci_base_dir/bin/debci-status --all --status-file > "${debci_status_dir}/packages.json".new
  mv "${debci_status_dir}/packages.json".new "${debci_status_dir}/packages.json"
}

generate_all_packages() {
  # clean up packages/
  if [ -d "$debci_packages_dir" ]; then
    find "$debci_packages_dir" -empty -delete
  fi
  local updated_packages="$(generate_packages)"
  if [ -n "$updated_packages" ]; then
    run_with_exclusive_lock "$debci_chdist_lock" debci-setup-chdist --quiet

    debci-generate-feeds $updated_packages
    generate_status_entry
    generate_history
    generate_packages_status
  fi
}

if [ -d "$debci_autopkgtest_incoming_dir" ]; then
  run_with_lock_or_exit $debci_generate_index_lock \
    generate_all_packages
fi
