#!/bin/bash

set -e

# Get the first domain of a comma separated list.
function get_base_domain {
  awk -F ',' '{print $1}' <<< "${1:?}" | tr -d ' ' | sed 's/\.$//'
}
export -f get_base_domain


# Run a acme-companion container
function run_le_container {
  local image="${1:?}"
  local name="${2:?}"
  shift 2
  local -a cli_args_arr
  
  while [[ $# -gt 0 ]]; do
  local flag="$1"

    case $flag in
      -c|--cli-args) #only one value per flag. Multiple args = use flag multiple times 
      local cli_args_arr_tmp
      IFS=' ' read -r -a cli_args_arr_tmp <<< "${2:?}"
      cli_args_arr+=("${cli_args_arr_tmp[0]}") #Head
      cli_args_arr+=("${cli_args_arr_tmp[*]:1}") #Tail
      shift 2
      ;;

      *) #Legacy Option
      local cli_args_str="${1:?}"
      for arg in $cli_args_str; do
        cli_args_arr+=("$arg")
      done
      shift
      ;;
    esac

  done

  if [[ "$SETUP" == '3containers' ]]; then
    cli_args_arr+=(--env "NGINX_DOCKER_GEN_CONTAINER=$DOCKER_GEN_CONTAINER_NAME")
  fi

  if [[ "$ACME_CA" == 'boulder' ]]; then
    cli_args_arr+=(--env "ACME_CA_URI=http://boulder:4001/directory")
    cli_args_arr+=(--network boulder_bluenet)
  elif [[ "$ACME_CA" == 'pebble' ]]; then
    cli_args_arr+=(--env "ACME_CA_URI=https://pebble:14000/dir")
    cli_args_arr+=(--env "CA_BUNDLE=/pebble.minica.pem")
    cli_args_arr+=(--network acme_net)
    cli_args_arr+=(--volume "${GITHUB_WORKSPACE}/pebble.minica.pem:/pebble.minica.pem")
  else
    return 1
  fi

  if docker run -d \
    --name "$name" \
    --volumes-from "$NGINX_CONTAINER_NAME" \
    --volume /var/run/docker.sock:/var/run/docker.sock:ro \
    "${cli_args_arr[@]}" \
    --env "DOCKER_GEN_WAIT=500ms:2s" \
    --env "TEST_MODE=true" \
    --env "DEBUG=1" \
    --label com.github.nginx-proxy.acme-companion.test-suite \
    "$image" > /dev/null; \
  then
    [[ "${DRY_RUN:-}" == 1 ]] && echo "Started letsencrypt container for test ${name%%_2*}"
  else
    echo "Could not start letsencrypt container for test ${name%%_2*}"
    return 1
  fi
  return 0
}
export -f run_le_container

# Run an nginx container
function run_nginx_container {
  local -a cli_args_arr

  while [[ $# -gt 0 ]]; do
  local flag="$1"

    case $flag in
      -h|--hosts)
      local le_host="${2:?}"
      local virtual_host="${le_host// /}"; virtual_host="${virtual_host//.,/,}"; virtual_host="${virtual_host%,}"
      shift 2
      ;;

      -n|--name)
      local container_name="${2:?}"
      shift 2
      ;;

      -c|--cli-args)
      local cli_args_arr_tmp
      IFS=' ' read -r -a cli_args_arr_tmp <<< "${2:?}"
      cli_args_arr+=("${cli_args_arr_tmp[0]}") #Head
      cli_args_arr+=("${cli_args_arr_tmp[*]:1}") #Tail
      shift 2
      ;;

      *) #Unknown option
      shift
      ;;
    esac
  done

  if [[ "$ACME_CA" == 'boulder' ]]; then
    cli_args_arr+=(--network boulder_bluenet)
  elif [[ "$ACME_CA" == 'pebble' ]]; then
    cli_args_arr+=(--network acme_net)
  else
    return 1
  fi

  [[ "${DRY_RUN:-}" == 1 ]] && echo "Starting $container_name nginx container, with VIRTUAL_HOST=$virtual_host, LETSENCRYPT_HOST=$le_host and the following cli arguments : ${cli_args_arr[*]}."
  
  if docker run --rm -d \
    --name "${container_name:-$virtual_host}" \
    -e "VIRTUAL_HOST=$virtual_host" \
    -e "LETSENCRYPT_HOST=$le_host" \
    --label com.github.nginx-proxy.acme-companion.test-suite \
    "${cli_args_arr[@]}" \
    nginx:alpine > /dev/null ; \
  then
    [[ "${DRY_RUN:-}" == 1 ]] && echo "Started $container_name nginx container."
  else
    echo "Failed to start $container_name nginx container, with VIRTUAL_HOST=$virtual_host, LETSENCRYPT_HOST=$le_host and the following cli arguments : ${cli_args_arr[*]}."
    return 1
  fi
  return 0
}
export -f run_nginx_container


# Wait for the /etc/nginx/conf.d/standalone-cert-$1.conf file to exist inside container $2
function wait_for_standalone_conf {
  local domain="${1:?}"
  local name="${2:?}"
  local timeout
  timeout="$(date +%s)"
  timeout="$((timeout + 120))"
  local target
  until docker exec "$name" [ -f "/etc/nginx/conf.d/standalone-cert-$domain.conf" ]; do
    if [[ "$(date +%s)" -gt "$timeout" ]]; then
      echo "Standalone configuration file for $domain was not generated under one minute, timing out."
      return 1
    fi
    sleep 0.1
  done
}
export -f wait_for_standalone_conf


# Wait for the /etc/nginx/certs/$1.crt symlink to exist inside container $2
function wait_for_symlink {
  local domain="${1:?}"
  local name="${2:?}"
  local expected_target="${3:-}"
  local timeout
  timeout="$(date +%s)"
  timeout="$((timeout + 120))"
  local target
  until docker exec "$name" [ -L "/etc/nginx/certs/$domain.crt" ]; do
    if [[ "$(date +%s)" -gt "$timeout" ]]; then
      echo "Symlink for $domain certificate was not generated under one minute, timing out."
      return 1
    fi
    sleep 0.1
  done
  [[ "${DRY_RUN:-}" == 1 ]] && echo "Symlink to $domain certificate has been generated."
  if [[ -n "$expected_target" ]]; then
    target="$(docker exec "$name" readlink "/etc/nginx/certs/$domain.crt")"
    if [[ "$target" != "$expected_target" ]]; then
      echo "The symlink to the $domain certificate is expected to point to $expected_target but point to $target instead."
      return 1
    elif [[ "${DRY_RUN:-}" == 1 ]]; then
      echo "The symlink is pointing to the file $target"
    fi
  fi
  return 0
}
export -f wait_for_symlink


# Wait for the /etc/nginx/certs/$1.crt symlink to be removed inside container $2
function wait_for_symlink_rm {
  local domain="${1:?}"
  local name="${2:?}"
  local timeout
  timeout="$(date +%s)"
  timeout="$((timeout + 120))"
  until docker exec "$name" [ ! -L "/etc/nginx/certs/$domain.crt" ]; do
    if [[ "$(date +%s)" -gt "$timeout" ]]; then
      echo "Certificate symlink for $domain was not removed under one minute, timing out."
      return 1
    fi
    sleep 0.1
  done
  [[ "${DRY_RUN:-}" == 1 ]] && echo "Symlink to $domain certificate has been removed."
  return 0
}
export -f wait_for_symlink_rm


# Attempt to grab the certificate from domain passed with -d/--domain
# then check if the subject either match or doesn't match the pattern
# passed with either -m/--match or -nm/--no-match
# If domain can't be reached return 1
function check_cert_subj {
  while [[ $# -gt 0 ]]; do
  local flag="$1"

    case $flag in
      -d|--domain)
      local domain="${2:?}"
      shift
      shift
      ;;

      -m|--match)
      local re="${2:?}"
      local match_rc=0
      local no_match_rc=1
      shift
      shift
      ;;

      -n|--no-match)
      local re="${2:?}"
      local match_rc=1
      local no_match_rc=0
      shift
      shift
      ;;

      *) #Unknown option
      shift
      ;;
    esac
  done

  if curl -k https://"$domain" &> /dev/null; then
    local cert_subject
    cert_subject="$(echo \
      | openssl s_client -showcerts -servername "$domain" -connect "$domain:443" 2>/dev/null \
      | openssl x509 -subject -noout)"
  else
    return 1
  fi

  if [[ "$cert_subject" =~ $re ]]; then
    return $match_rc
  else
    return $no_match_rc
  fi
}
export -f check_cert_subj


# Wait for a successful https connection to domain passed with -d/--domain then wait
#   - until the served certificate isn't the default one (default behavior)
#   - until the served certificate subject match a string (--subject-match)
function wait_for_conn {
  local action
  local domain
  local string

  while [[ $# -gt 0 ]]; do
  local flag="$1"

    case $flag in
      -d|--domain)
      domain="${2:?}"
      shift
      shift
      ;;

      --subject-match)
      action='--match'
      string="$2"
      shift
      shift
      ;;

      *) #Unknown option
      shift
      ;;
    esac
  done

  local timeout
  timeout="$(date +%s)"
  timeout="$((timeout + 120))"
  action="${action:---no-match}"
  string="${string:-letsencrypt-nginx-proxy-companion}"

  until check_cert_subj --domain "$domain" "$action" "$string"; do
    if [[ "$(date +%s)" -gt "$timeout" ]]; then
      echo "Could not connect to $domain using https under two minutes, timing out."
      return 1
    fi
    sleep 0.1
  done
  [[ "${DRY_RUN:-}" == 1 ]] && echo "Connection to $domain using https was successful."
  return 0
}
export -f wait_for_conn


# Get the expiration date in unix epoch of domain $1 inside container $2
function get_cert_expiration_epoch {
  local domain="${1:?}"
  local name="${2:?}"
  local cert_expiration
  cert_expiration="$(docker exec "$name" openssl x509 -noout -enddate -in "/etc/nginx/certs/$domain.crt")"
  cert_expiration="$(echo "$cert_expiration" | cut -d "=" -f 2)"
  if [[ "$(uname)" == 'Darwin' ]]; then
    cert_expiration="$(date -j -f "%b %d %T %Y %Z" "$cert_expiration" "+%s")"
  else
    cert_expiration="$(date -d "$cert_expiration" "+%s")"
  fi
  echo "$cert_expiration"
}
export -f get_cert_expiration_epoch
