#!/usr/bin/env bash
set -o errexit -o errtrace -o functrace -o nounset -o pipefail

########################################################################
# Logging helpers
########################################################################
readonly COLOR_RED=1
readonly COLOR_GREEN=2
readonly COLOR_YELLOW=3

# Prefix a date to a log line and output to stderr
logger::stamp(){
  local color="$1"
  local level="$2"
  local i
  shift
  shift
  [ ! "$TERM" ] || [ ! -t 2 ] || >&2 tput setaf "$color"
  for i in "$@"; do
    >&2 printf "[%s] [%s] %s\\n" "$(date)" "$level" "$i"
  done
  [ ! "$TERM" ] || [ ! -t 2 ] || >&2 tput op
}

logger::info(){
  logger::stamp "$COLOR_GREEN" "INFO" "$@"
}

logger::warning(){
  logger::stamp "$COLOR_YELLOW" "WARNING" "$@"
}

logger::error(){
  logger::stamp "$COLOR_RED" "ERROR" "$@"
}

########################################################################
# Handle exit code and errors of random command
########################################################################
wrap::err(){
  local args=()
  local actualExit=0
  local expectedExit=0
  local ret=0

  for i in "$@"; do
    if [ "${i:0:9}" == "--stderr=" ]; then
      local expectedStderr="${i:9}"
    elif [ "${i:0:7}" == "--exit=" ]; then
      expectedExit="${i:7}"
      expectedExit="${expectedExit:-0}"
    else
      args+=("$i")
    fi
  done

  logger::info " -> ${args[*]}"

  exec 3>&1
  actualStderr="$("${args[@]}" 2>&1 1>&3)" || actualExit="$?"

  [ "$expectedExit" == "$actualExit" ] || {
    logger::error " -> Expected exit code: $expectedExit" " -> Actual exit code  : $actualExit"
    logger::error " -> Stderr was:"
    >&2 jq <<<"$actualStderr" 2>/dev/null || {
      >&2 echo "$actualStderr"
      logger::error " -> Also, stderr is not json"
    }
    ret=1
  }

  [ -z "${expectedStderr+x}" ] || [ "$expectedStderr" == "$actualStderr" ] || {
    logger::error " -> Expected stderr:"
    >&2 jq <<<"$expectedStderr" 2>/dev/null || {
      >&2 echo "$expectedStderr"
    }
    logger::error " -> Actual stderr  :"
    >&2 jq <<<"$actualStderr" 2>/dev/null || {
      >&2 echo "$actualStderr"
      logger::error " -> Also, stderr is not json ^"
    }
    ret=1
  }

  exec 3>&-
  return "$ret"
}

########################################################################
# Main test function
#    argument 1 is a test description
#    to test the exit code, pass --exit=int (if not provided, will test that the command exits succesfully)
#    to test the value of stderr, pass --stderr=string (if not provided, stderr is not verified)
#    to test the value of stdout, pass --stdout=string (if not provided, stdout is not verified)
#    any other argument is the command that is going to be run
# Example:
#    test dagger compute somecue --exit=1 --stderr=expectederror
########################################################################
test::one(){
  local testDescription="$1"
  shift
  local args=()
  local ret=0

  for i in "$@"; do
    if [ "${i:0:9}" == "--stdout=" ]; then
      local expectedStdout="${i:9}"
    else
      args+=("$i")
    fi
  done

  logger::info "$testDescription"

  local actualStdout
  actualStdout="$(wrap::err "${args[@]}")" || {
    ret=1
  }

  [ -z "${expectedStdout+x}" ] || [ "$expectedStdout" == "$actualStdout" ] || {
    exec 3>&-
    logger::error " -> Expected stdout: $expectedStdout" " -> Actual stdout  : $actualStdout"
    ret=1
  }

  [ "$ret" != 0 ] || logger::info " -> Success"
  return "$ret"
}

disable(){
  logger::warning "Test \"$2\" has been disabled."
}