#!/usr/bin/env bash

if [[ ! -z "${DEBUG}" ]]; then
  set -x
fi


SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
SUITE=$(caller)
SUITE=$(basename "${SUITE}" .sh )

tabs 1
declare -i PADDING_LEVEL=0
declare -i STEP=1
declare -a REQUIRED_COMPONENTS

find_selenium_dir() {
  TEST_PATH=$1
  FOUND=""
  while [[ $TEST_PATH != "" && $FOUND == "" ]]; do
    FILENAME=$(basename "$TEST_PATH")
    if [[ $FILENAME == "selenium" ]]; then
      FOUND=$TEST_PATH
    fi
    TEST_PATH="$(dirname $TEST_PATH)"
  done
  echo $FOUND
}

SELENIUM_ROOT_FOLDER=$(find_selenium_dir $SCRIPT)
TEST_DIR=$SELENIUM_ROOT_FOLDER/test
BIN_DIR=$SELENIUM_ROOT_FOLDER/bin
LOGS=${SELENIUM_ROOT_FOLDER}/logs/${SUITE}
SCREENS=${SELENIUM_ROOT_FOLDER}/screens/${SUITE}
CONF_DIR=/tmp/selenium/${SUITE}
ENV_FILE=$CONF_DIR/.env

rm -rf $CONF_DIR

for f in $SCRIPT/components/*; do
  if [[ ! "$f" == *README.md ]]
  then
    source $f;
  fi
done

parse_arguments() {
  if [[ "$#" -gt 0 ]]
  then
    if [[ "$1" == "start-rabbitmq" ]]
    then
      echo "start-rabbitmq"
    elif [[ "$1" == "start-others" ]]
      then
      echo "start-others"
    elif [[ "$1" == "ensure-others" ]]
      then
      echo "ensure-others"
    elif [[ "$1" == "stop-others" ]]
      then
      echo "stop-others"
    elif [[ "$1" == "stop-rabbitmq" ]]
      then
      echo "stop-rabbitmq"
    elif [[ "$1" == "test" ]]
      then
      echo "test $2"
    fi
  else
    echo "run"
  fi
}

COMMAND=$(parse_arguments $@)




print() {
  tabbing=""
  if [[  $PADDING_LEVEL -gt 0 ]]; then
    for i in $(seq $PADDING_LEVEL); do
        tabbing="$tabbing\t"
    done
  fi
  echo -e "$tabbing$1"
}

begin() {
  print "\n[$STEP] $@"
  PADDING_LEVEL=$(($PADDING_LEVEL + 1))
  STEP=$(($STEP + 1))
}
end() {
  PADDING_LEVEL=$(($PADDING_LEVEL - 1))
  print "$@"
}
ensure_docker_network() {
  begin "Ensuring $DOCKER_NETWORK network ..."
  if [ ! "$(docker network ls | grep $DOCKER_NETWORK)" ]; then
    print "> DOCKER_NETWORK: $DOCKER_NETWORK created"
    docker network create $DOCKER_NETWORK
  fi
  end "$DOCKER_NETWORK network exists"
}
init_suite() {
  TEST_CASES_DIR=$(realpath ${TEST_DIR}${TEST_CASES_PATH:?"missing TEST_CASES_PATH"})
  if [ -z "${TEST_CONFIG_PATH}" ]; then TEST_CONFIG_DIR=$TEST_CASES_DIR
  else TEST_CONFIG_DIR=$(realpath ${TEST_DIR}${TEST_CONFIG_PATH})
  fi
  DOCKER_NETWORK=${DOCKER_NETWORK:-rabbitmq_net}

  begin "Initializing suite $SUITE ..."
  print "> REQUIRED_COMPONENTS: ${REQUIRED_COMPONENTS[*]}"
  print "> TEST_CASES_DIR: ${TEST_CASES_DIR} "
  print "> TEST_CONFIG_DIR: ${TEST_CONFIG_DIR} "
  print "> DOCKER_NETWORK: ${DOCKER_NETWORK} "
  print "> initial PROFILES: ${PROFILES} "
  print "> (+) ADDON_PROFILES: ${ADDON_PROFILES} "
  PROFILES="${PROFILES} ${ADDON_PROFILES}"
  print "> (=) final PROFILES: ${PROFILES} "
  print "> ENV_FILE: ${ENV_FILE} "
  print "> COMMAND: ${COMMAND}"
  end "Initialized suite"

  mkdir -p ${LOGS}/${SUITE}
  mkdir -p ${SCREENS}/${SUITE}
}

build_mocha_image() {
  begin "Ensuring mocha-test image ..."
  tag=($(md5sum $SELENIUM_ROOT_FOLDER/package.json))
  print "> tag : $tag"
  if [[ $(docker images -q mocha-test:$tag 2> /dev/null) == "" ]]; then
    docker build -t mocha-test:$tag  --target test $SCRIPT/..
    print "> Built docker image mocha-test:$tag"
  fi
  end "mocha-test image exists"
}

kill_container_if_exist() {
  if docker stop $1 &> /dev/null; then
     docker rm $1 &> /dev/null
  fi
}
wait_for_message() {
  attemps_left=10
  while ! docker logs $1 2>&1 | grep -q "$2";
  do
      sleep 5
      print "Waiting 5sec for $1 to start ($attemps_left attempts left )..."
      ((attemps_left--))
      if [[ "$attemps_left" -lt 1 ]]; then
        print "Timed out waiting"
        save_container_log $1
        exit 1
      fi
  done
}

wait_for_oidc_endpoint() {
  NAME=$1
  BASE_URL=$2
  if [[ $BASE_URL == *"localhost"** || $BASE_URL == *"0.0.0.0"** ]]; then
    wait_for_oidc_endpoint_local $@
  else
    wait_for_oidc_endpoint_docker $@
  fi
}
wait_for_oidc_endpoint_local() {
  NAME=$1
  BASE_URL=$2
  CURL_ARGS="-L --fail "
  DELAY_BETWEEN_ATTEMPTS=5
  if [[ $# -eq 3 ]]; then
    CURL_ARGS="$CURL_ARGS --cacert $3"
    DELAY_BETWEEN_ATTEMPTS=10
  fi
  max_retry=10
  counter=0
  print "Waiting for OIDC discovery endpoint $NAME ... (BASE_URL: $BASE_URL)"
  until (curl $CURL_ARGS ${BASE_URL}/.well-known/openid-configuration >/dev/null 2>&1)
  do
    sleep $DELAY_BETWEEN_ATTEMPTS
    [[ counter -eq $max_retry ]] && print "Failed!" && exit 1
    print "Trying again. Try #$counter"
    ((counter++))
  done
  sleep 20
}
wait_for_oidc_endpoint_docker() {
  NAME=$1
  BASE_URL=$2
  CURL_ARGS="-L --fail "
  DOCKER_ARGS="--rm --net ${DOCKER_NETWORK} "
  DELAY_BETWEEN_ATTEMPTS=5
  if [[ $# -gt 2  ]]; then
    DOCKER_ARGS="$DOCKER_ARGS -v $3:/tmp/ca_certificate.pem"
    CURL_ARGS="$CURL_ARGS --cacert /tmp/ca_certificate.pem"
    DELAY_BETWEEN_ATTEMPTS=10
  fi
  max_retry=10
  counter=0
  print "Waiting for OIDC discovery endpoint $NAME ... (BASE_URL: $BASE_URL)"
  until (docker run $DOCKER_ARGS curlimages/curl:7.85.0 $CURL_ARGS ${BASE_URL}/.well-known/openid-configuration >/dev/null 2>&1)
  do
    sleep $DELAY_BETWEEN_ATTEMPTS
    [[ counter -eq $max_retry ]] && print "Failed!" && exit 1
    print "Trying again. Try #$counter"
    ((counter++))
  done
  sleep 20
}
calculate_rabbitmq_url() {
  echo "${RABBITMQ_SCHEME:-http}://$1${PUBLIC_RABBITMQ_PATH:-$RABBITMQ_PATH}"
}

wait_for_url() {
  BASE_URL=$1
  if [[ $BASE_URL == *"localhost"** ]]; then
    wait_for_url_local $BASE_URL
  else
    wait_for_url_docker $BASE_URL
  fi
}
wait_for_url_local() {
  url=$1
  max_retry=10
  counter=0
  until (curl -L -f -v $url >/dev/null 2>&1)
  do
    print "Waiting for $url to start (local)"
    sleep 5
    [[ counter -eq $max_retry ]] && print "Failed!" && exit 1
    print "Trying again. Try #$counter"
    ((counter++))
  done
}
wait_for_url_docker() {
  url=$1
  max_retry=10
  counter=0
  until (docker run --net ${DOCKER_NETWORK} --rm curlimages/curl:7.85.0 -L -f -v $url >/dev/null 2>&1)
  do
    print "Waiting for $url to start (docker)"
    sleep 5
    [[ counter -eq $max_retry ]] && print "Failed!" && exit 1
    print "Trying again. Try #$counter"
    ((counter++))
  done
}

test_on_cluster() {
    IFS=', ' read -r -a array <<< "$RABBITMQ_CLUSTER_NODES"
    begin "Running against all nodes in cluster $RABBITMQ_CLUSTER_NODES :"
    for item in "${array[@]}"
    do
        RMQ_HOST_0=${RABBITMQ_HOST:-rabbitmq:15672}
        RMQ_HOST=$(rewrite_rabbitmq_hostname ${item} $RMQ_HOST_0)
        PUBLIC_RMQ_HOST_0=${PUBLIC_RABBITMQ_HOST:-$RMQ_HOST}
        PUBLIC_RMQ_HOST=$(rewrite_rabbitmq_hostname ${item} $PUBLIC_RMQ_HOST_0)
        RMQ_URL=$(calculate_rabbitmq_url $PUBLIC_RMQ_HOST)
        RMQ_HOSTNAME=${item}
        _test $RMQ_HOST \
              $PUBLIC_RMQ_HOST \
              $RMQ_URL \
              $RMQ_HOSTNAME
        TEST_RESULT=$?
        if [ $TEST_RESULT -ne 0 ]; then
            break
        fi
    done
    end "Finishing running test ($TEST_RESULT)"
}
rewrite_rabbitmq_hostname() {
    IFS=':' read -r -a array <<< "$2"
    if [ "${array[0]}" == "rabbitmq" ]; then
        echo "${2//rabbitmq/$1}"
    else
        echo "$2"
    fi
}
test() {
  if [[ "$PROFILES" == *"cluster"* && ! -z "$RABBITMQ_CLUSTER_NODES" ]]; then
      test_on_cluster
  else
      RMQ_HOST=${RABBITMQ_HOST:-rabbitmq:15672}
      PUBLIC_RMQ_HOST=${PUBLIC_RABBITMQ_HOST:-$RMQ_HOST}
      _test $RABBITMQ_HOST \
            $PUBLIC_RMQ_HOST \
            $(calculate_rabbitmq_url $PUBLIC_RMQ_HOST) \
            ${RABBITMQ_HOSTNAME:-rabbitmq}
  fi
}

_test() {
  RMQ_HOST=$1
  PUBLIC_RMQ_HOST=$2
  RMQ_URL=$3
  RMQ_HOSTNAME=$4

  kill_container_if_exist mocha
  begin "Running tests against $RMQ_HOSTNAME with these env variable:"

  SELENIUM_TIMEOUT=${SELENIUM_TIMEOUT:-20000}
  SELENIUM_POLLING=${SELENIUM_POLLING:-500}

  print "> SELENIUM_TIMEOUT: ${SELENIUM_TIMEOUT}"
  print "> SELENIUM_POLLING: ${SELENIUM_POLLING}"
  print "> RABBITMQ_HOST: ${RMQ_HOST}"
  print "> RABBITMQ_HOSTNAME: ${RMQ_HOSTNAME}"
  print "> PUBLIC_RABBITMQ_HOST: ${PUBLIC_RMQ_HOST}"
  print "> RABBITMQ_PATH: ${RABBITMQ_PATH}"
  print "> RABBITMQ_URL: ${RMQ_URL}"
  print "> UAA_URL: ${UAA_URL}"
  print "> FAKEPORTAL_URL: ${FAKEPORTAL_URL}"
  mocha_test_tag=($(md5sum $SELENIUM_ROOT_FOLDER/package.json))

  print "> OAUTH_NODE_EXTRA_CA_CERTS: ${OAUTH_NODE_EXTRA_CA_CERTS}"
  MOUNT_NODE_EXTRA_CA_CERTS=${TEST_DIR}/${OAUTH_NODE_EXTRA_CA_CERTS}
  print "> MOUNT_NODE_EXTRA_CA_CERTS: ${MOUNT_NODE_EXTRA_CA_CERTS}"

  docker run \
    --rm \
    --name mocha \
    --net ${DOCKER_NETWORK} \
    --env RABBITMQ_URL=${RMQ_URL} \
    --env RABBITMQ_HOSTNAME=${RMQ_HOSTNAME} \
    --env UAA_URL=${UAA_URL} \
    --env FAKE_PORTAL_URL=${FAKEPORTAL_URL} \
    --env RUN_LOCAL=false \
    --env SELENIUM_TIMEOUT=${SELENIUM_TIMEOUT} \
    --env SELENIUM_POLLING=${SELENIUM_POLLING} \
    --env PROFILES="${PROFILES}" \
    --env ENV_FILE="/code/.env" \
    --env NODE_EXTRA_CA_CERTS=/nodejs/ca.pem \
    -v ${MOUNT_NODE_EXTRA_CA_CERTS}:/nodejs/ca.pem \
    -v ${TEST_DIR}:/code/test \
    -v ${SCREENS}:/screens \
    -v ${ENV_FILE}:/code/.env \
    mocha-test:${mocha_test_tag} test /code/test${TEST_CASES_PATH}

  TEST_RESULT=$?
  end "Finishing running test ($TEST_RESULT)"
  return $TEST_RESULT
}

save_logs() {
  mkdir -p $LOGS
  save_container_logs selenium
}
save_container_logs() {
  echo "Saving logs for $1"
  if docker container ls | grep $1 >/dev/null 2>&1; then
    docker logs $1 &> $LOGS/$1.log
  else
    echo "$1 not running"
  fi
}
save_container_log() {
  echo "Saving container $1 logs to $LOGS/$1.log ..."
  docker logs $1 &> $LOGS/$1.log
}
profiles_with_local_or_docker() {
  if [[ "$PROFILES" != *"local"* && "$PROFILES" != *"docker"* ]]; then
    echo "$PROFILES docker"
  else
    echo "$PROFILES"
  fi
}
generate_env_file() {
    begin "Generating env file ..."
    mkdir -p $CONF_DIR
    ${BIN_DIR}/gen-env-file $TEST_CONFIG_DIR $ENV_FILE
    source $ENV_FILE
    end "Finished generating env file."
}
run() {
  runWith rabbitmq
}
runWith() {
  if [[ "$COMMAND" == "run" ]]
  then
    run_on_docker_with $@
  else
    run_local_with $@
  fi
}

run_local_with() {
  export PROFILES="local ${PROFILES}"
  determine_required_components_excluding_rabbitmq $@
  init_suite
  ensure_docker_network
  generate_env_file
  build_mocha_image


  if [[ "$COMMAND" == "start-rabbitmq" ]]
  then
    start_local_rabbitmq
elif [[ "$COMMAND" == "stop-rabbitmq" ]]
  then
    stop_local_rabbitmq
  elif [[ "$COMMAND" == "start-others" ]]
  then
    start_local_others
  elif [[ "$COMMAND" == "ensure-others" ]]
  then
    ensure_local_others
  elif [[ "$COMMAND" == "stop-others" ]]
  then
    teardown_local_others
  elif [[ "$COMMAND" == "stop-rabbitmq" ]]
  then
    stop_local_rabbitmq
  elif [[ "$COMMAND" =~ test[[:space:]]*([^[:space:]]*) ]]
  then
    test_local ${BASH_REMATCH[1]}
  fi
}
determine_required_components_including_rabbitmq() {
  if [[ "$@" != *"rabbitmq"* ]]; then
    REQUIRED_COMPONENTS+=("rabbitmq")
  fi
  for (( i=1; i<=$#; i++)) {
    eval val='$'$i
    REQUIRED_COMPONENTS+=( "$val" )
  }
}
determine_required_components_excluding_rabbitmq() {
  for (( i=1; i<=$#; i++)) {
    if [[ $i != "rabbitmq" ]]; then
      eval val='$'$i
      REQUIRED_COMPONENTS+=( "$val" )
    fi
  }
}
run_on_docker_with() {
  determine_required_components_including_rabbitmq $@
  export PROFILES=`profiles_with_local_or_docker`
  init_suite
  ensure_docker_network
  generate_env_file
  build_mocha_image
  start_selenium

  trap teardown_components EXIT

  start_components
  test
  TEST_RESULT=$?
  save_logs
  save_components_logs

  kill_container_if_exist selenium

  exit $TEST_RESULT
}
start_local_others() {
  if [[ $REQUIRED_COMPONENTS == "" ]]; then
    print "There are no other components"
  else
    start_components
  fi
}
ensure_local_others() {
  if [[ $REQUIRED_COMPONENTS == "" ]]; then
    print "There are no other components"
  else
    ensure_components
  fi
}
teardown_local_others() {
  if [[ $REQUIRED_COMPONENTS == "" ]]; then
    print "There are no other components"
  else
    teardown_components
  fi
}
test_local() {
  begin "Running local test ${1:-}"

  RABBITMQ_HOST=${RABBITMQ_HOST:-rabbitmq:15672}
  PUBLIC_RABBITMQ_HOST=${PUBLIC_RABBITMQ_HOST:-$RABBITMQ_HOST}
  export RABBITMQ_URL=$(calculate_rabbitmq_url $PUBLIC_RABBITMQ_HOST)
  export RABBITMQ_HOSTNAME=${RABBITMQ_HOSTNAME:-rabbitmq}
  export RABBITMQ_AMQP_USERNAME=${RABBITMQ_AMQP_USERNAME}
  export RABBITMQ_AMQP_PASSWORD=${RABBITMQ_AMQP_PASSWORD}
  export SELENIUM_TIMEOUT=${SELENIUM_TIMEOUT:-20000}
  export SELENIUM_POLLING=${SELENIUM_POLLING:-500}

  print "> SELENIUM_TIMEOUT: ${SELENIUM_TIMEOUT}"
  print "> SELENIUM_POLLING: ${SELENIUM_POLLING}"
  print "> RABBITMQ_HOST: ${RABBITMQ_HOST}"
  print "> RABBITMQ_HOSTNAME: ${RABBITMQ_HOSTNAME}"
  print "> PUBLIC_RABBITMQ_HOST: ${PUBLIC_RABBITMQ_HOST}"
  print "> RABBITMQ_PATH: ${RABBITMQ_PATH}"
  print "> RABBITMQ_URL: ${RABBITMQ_URL}"
  print "> UAA_URL: ${UAA_URL}"
  print "> FAKE_PORTAL_URL: ${FAKE_PORTAL_URL}"
  print "> OAUTH_NODE_EXTRA_CA_CERTS: ${OAUTH_NODE_EXTRA_CA_CERTS}"
  MOUNT_NODE_EXTRA_CA_CERTS=${TEST_DIR}/${OAUTH_NODE_EXTRA_CA_CERTS}
  print "> MOUNT_NODE_EXTRA_CA_CERTS: ${MOUNT_NODE_EXTRA_CA_CERTS}"

  export RUN_LOCAL=true
  export SCREENSHOTS_DIR=${SCREENS}

  export PROFILES
  export ENV_FILE
  export NODE_EXTRA_CA_CERTS=$MOUNT_NODE_EXTRA_CA_CERTS
  npm test $TEST_CASES_DIR/$1

}
ensure_components() {
  for i in "${REQUIRED_COMPONENTS[@]}"
  do
    start="ensure_$i"
    $start
  done
}
start_components() {
  for i in "${REQUIRED_COMPONENTS[@]}"
  do
    start="start_$i"
    $start
  done
}

teardown_components() {
  begin "Tear down ..."
  for i in "${REQUIRED_COMPONENTS[@]}"
  do
    local component="$i"
    stop="stop_$i"
    type "$stop" &>/dev/null && $stop || kill_container_if_exist "$component"
    print "Tear down $component"
  done
  end "Finished teardown"
}
save_components_logs() {
  begin "Saving Logs to $LOGS for ${REQUIRED_COMPONENTS[@]} ..."
  for i in "${REQUIRED_COMPONENTS[@]}"
  do
    local component="$i"
    save="save_logs_$i"
    type "$save" &>/dev/null && $save || save_container_logs "$component"
    print "Saving logs for component $component"
  done
  end "Finished saving logs"
}
