#!/bin/bash

if [ "$1" == "--show-password" ]; then
  HSADMINNG_JWT_SHOW_PASSWORD=yes
  shift
else
  HSADMINNG_JWT_SHOW_PASSWORD=
fi

if [ "$1" == "--trace" ]; then
  function trace() {
    echo "$*" >&2
  }
  function doCurl() {
    set -x
    if [ -z "$HSADMINNG_JWT_ASSUME" ]; then
      curl --no-progress-meter --show-error --fail-with-body \
           --header "Authorization: Bearer $HSADMINNG_JWT_TOKEN" \
           "$@"
    else
      curl --no-progress-meter --show-error --fail-with-body \
           --header "Authorization: Bearer $HSADMINNG_JWT_TOKEN" \
           --header "assumed-roles: $HSADMINNG_JWT_ASSUME" \
           "$@"
    fi
    set +x
    echo
  }
  shift
else
  function trace() {
    : # noop
  }
  function doCurl() {
    if [ -z "$HSADMINNG_JWT_ASSUME" ]; then
      curl --fail-with-body --header "Authorization: Bearer $HSADMINNG_JWT_TOKEN" "$@"
    else
      curl --fail-with-body \
           --header "Authorization: Bearer $HSADMINNG_JWT_TOKEN" \
           --header "assumed-roles: $HSADMINNG_JWT_ASSUME" \
           "$@"
      echo
    fi
  }
fi

export HSADMINNG_JWT_ASSUME_HEADER
if [ -f ~/.cas-curl-assume ]; then
  HSADMINNG_JWT_ASSUME="$(cat ~/.cas-curl-assume)"
else
  HSADMINNG_JWT_ASSUME=
fi

if [ -z "$HSADMINNG_JWT_TOKEN_URL" ]; then
  cat >&2 <<EOF
  ERROR: environment incomplete

  please set the following environment variables:
  export HSADMINNG_JWT_TOKEN_URL=https://login.hostsharing.net/oauth/token
  export HSADMINNG_JWT_USERNAME=<<optionally, your username, or leave empty after '='>>
  export HSADMINNG_JWT_PASSWORD=<<optionally, your password, or leave empty after '='>>
EOF
  exit 1
fi

function jwtCurlDocumentation() {
  cat <<EOF
  curl-wrapper utilizing JWT-authentication for hsadmin-ng
  usage: $0 [--trace] [--show-password] <<command>> [parameters]

  commands:
EOF
  # filters out help texts (containing double-# and following lines with leading single-#) from the commands itself
  # (the '' makes sure that this line is not found, just the lines with actual help texts)
  sed -n '/#''#/ {x; p; x; s/#''#//; p; :a; n; /^[[:space:]]*#/!b; s/^[[:space:]]*#//; p; ba}' <$0
}

function jwtLogin() {
  # JWT token exists and not expired?
  if [ -f ~/.jwt-token ]; then
    # Check if token is still valid (simple expiry check - you might want to add JWT parsing for more precise validation)
    if find ~/.jwt-token -type f -size +0c -mmin -55 2>/dev/null | grep -q .; then
      HSADMINNG_JWT_TOKEN=$(<~/.jwt-token)
      return
    fi
  fi

  if [ -z "$HSADMINNG_JWT_USERNAME" ]; then
    read -e -p "Username: " HSADMINNG_JWT_USERNAME
  fi

  if [ -z "$HSADMINNG_JWT_PASSWORD" ]; then
    read -s -e -p "Password: " HSADMINNG_JWT_PASSWORD
  fi

  if [ "$HSADMINNG_JWT_SHOW_PASSWORD" == "yes" ]; then
    HSADMINNG_JWT_PASSWORD_DISPLAY=$HSADMINNG_JWT_PASSWORD
  else
    HSADMINNG_JWT_PASSWORD_DISPLAY="<<password hidden - use --show-password to show>>"
  fi

  # OAuth2 Resource Owner Password Credentials Grant (public client)
  trace "+ curl --no-progress-meter --fail-with-body --show-error -X POST \
        -H 'Content-Type: application/x-www-form-urlencoded' \
        -d \"grant_type=password&client_id=$HSADMINNG_JWT_CLIENT_ID&client_secret=$HSADMINNG_JWT_CLIENT_SECRET&username=$HSADMINNG_JWT_USERNAME&password=$HSADMINNG_JWT_PASSWORD_DISPLAY\" \
        $HSADMINNG_JWT_TOKEN_URL -o ~/.jwt-token.response"

  JWT_RESPONSE=$(curl --no-progress-meter --fail-with-body --show-error -X POST \
    -H 'Content-Type: application/x-www-form-urlencoded' \
    -d "grant_type=password&client_id=$HSADMINNG_JWT_CLIENT_ID&client_secret=$HSADMINNG_JWT_CLIENT_SECRET&username=$HSADMINNG_JWT_USERNAME&password=$HSADMINNG_JWT_PASSWORD_DISPLAY" \
    $HSADMINNG_JWT_TOKEN_URL 2>&1 | tee ~/.jwt-token.response)

  # Extract access token from JSON response
  HSADMINNG_JWT_TOKEN=$(echo "$JWT_RESPONSE" | grep -o '"access_token":"[^"]*' | cut -d'"' -f4)

  if [ -z "$HSADMINNG_JWT_TOKEN" ]; then
    echo "ERROR: could not get JWT access token: $JWT_RESPONSE" >&2
    echo >&2
    exit 1
  fi

  echo "$HSADMINNG_JWT_TOKEN" > ~/.jwt-token
  trace "JWT Token acquired (length: ${#HSADMINNG_JWT_TOKEN})"
}

function jwtLogout() {
  rm -f ~/.jwt-token ~/.jwt-token.response
}

function jwtValidate() {
  if [ ! -f ~/.jwt-token ]; then
    echo "ERROR: no JWT token found, please login first" >&2
    exit 1
  fi

  HSADMINNG_JWT_TOKEN=$(<~/.jwt-token)

  # Simple JWT validation - decode payload (base64 decode the middle part)
  JWT_PAYLOAD=$(echo "$HSADMINNG_JWT_TOKEN" | cut -d'.' -f2)
  # Add padding if needed for base64 decoding
  case $((${#JWT_PAYLOAD} % 4)) in
    2) JWT_PAYLOAD="${JWT_PAYLOAD}==" ;;
    3) JWT_PAYLOAD="${JWT_PAYLOAD}=" ;;
  esac

  if command -v base64 >/dev/null 2>&1; then
    JWT_DECODED=$(echo "$JWT_PAYLOAD" | base64 -d 2>/dev/null)
    if [ $? -eq 0 ]; then
      OAUTH_USER=$(echo "$JWT_DECODED" | grep -o '"sub":"[^"]*' | cut -d'"' -f4)
      if [ -z "$OAUTH_USER" ]; then
        OAUTH_USER=$(echo "$JWT_DECODED" | grep -o '"preferred_username":"[^"]*' | cut -d'"' -f4)
      fi
      if [ -z "$OAUTH_USER" ]; then
        OAUTH_USER=$(echo "$JWT_DECODED" | grep -o '"username":"[^"]*' | cut -d'"' -f4)
      fi

      if [ -n "$OAUTH_USER" ]; then
        echo "OAuth User: $OAUTH_USER"
        # Check expiry
        EXP=$(echo "$JWT_DECODED" | grep -o '"exp":[0-9]*' | cut -d':' -f2)
        if [ -n "$EXP" ]; then
          CURRENT_TIME=$(date +%s)
          if [ "$EXP" -lt "$CURRENT_TIME" ]; then
            echo "WARNING: JWT token has expired" >&2
          else
            echo "Token expires: $(date -d "@$EXP" 2>/dev/null || date -r "$EXP" 2>/dev/null || echo "unknown")"
          fi
        fi
      else
        echo "Could not extract user from JWT token" >&2
      fi
    else
      echo "Could not decode JWT token" >&2
    fi
  else
    echo "JWT token present but cannot validate (base64 command not available)" >&2
  fi
}

function jwtToken() {
  if [ ! -f ~/.jwt-token ]; then
    echo "ERROR: no JWT token found, please login first" >&2
    exit 1
  fi
  HSADMINNG_JWT_TOKEN=$(<~/.jwt-token)
  echo "$HSADMINNG_JWT_TOKEN"
}

case "${1,,}" in

  # -- generic commands --------------------------------------------------------------------------

  ""|"-h"|"--help"|"help") ## prints documentation about commands and options
      jwtCurlDocumentation
      exit
    ;;

  "env") ## prints all related HSADMINNG_JWT_... environment variables; use '--show-password' to show the password as well
  #       example: jwt-curl  -show-password env
    echo "export HSADMINNG_JWT_TOKEN_URL=$HSADMINNG_JWT_TOKEN_URL"
    echo "export HSADMINNG_JWT_CLIENT_ID=$HSADMINNG_JWT_CLIENT_ID"
    echo "export HSADMINNG_JWT_CLIENT_SECRET=$HSADMINNG_JWT_CLIENT_SECRET"
    echo "export HSADMINNG_JWT_USERNAME=$HSADMINNG_JWT_USERNAME"
    if [ "$HSADMINNG_JWT_SHOW_PASSWORD" == "yes" ]; then
      echo "export HSADMINNG_JWT_PASSWORD=$HSADMINNG_JWT_PASSWORD"
    elif [ -z "$HSADMINNG_JWT_PASSWORD" ]; then
      echo "export HSADMINNG_JWT_PASSWORD=#<<not given>>"
    else
      echo "export HSADMINNG_JWT_PASSWORD=#<<given, but hidden - add --show-password to show>>"
    fi
    ;;

  # --- authentication-related commands ------------------------------------------------------------

  "login") ## reads username+password and fetches JWT access token (bypasses HSADMINNG_JWT_USERNAME+HSADMINNG_JWT_PASSWORD)
  #       example: jwt-curl login
    jwtLogout
    export HSADMINNG_JWT_USERNAME=
    export HSADMINNG_JWT_PASSWORD=
    jwtLogin
    ;;
  "assume") ## assumes the given comma-separated roles
  #       example using object-id-name: jwt-curl assume 'hs_office.relation#ExampleMandant-with-PARTNER-ExamplePartner:AGENT'
  #       example using object-uuid:    jwt-curl assume 'hs_office.relation#1d3bc468-c5c8-11ef-9d0d-4751ecfda2b7:AGENT'
    shift
    if [ -z "$1" ]; then
      echo "ERROR: requires comma-separated list of roles to assume" >&2
      exit 1
    fi
    echo "$1" >~/.cas-curl-assume
    ;;
  "unassume") ## do not assume any particular role anymore, use the plain user as RBAC subject
    rm -f ~/.cas-curl-assume
    ;;
  "token") ## prints the current JWT access token
    jwtToken
    ;;
  "validate") ## validates current JWT token and prints currently logged in user
    jwtValidate
    ;;
  "logout") ## logout, deletes JWT token
    jwtLogout
    ;;

  # --- HTTP-commands ----------------------------------------------------------------------

  "get") ## HTTP GET, add URL as parameter
  #       example: jwt-curl GET http://localhost:8080/api/hs/office/partners/P-10003 | jq
  #       hint: '| jq' is just for human-readable formatted JSON output
    shift
    jwtLogin
    doCurl "$*"
    ;;
  "post") ## HTTP POST, add curl options to specify the request body and the URL as last parameter
  #       example: jwt-curl POST \
  #                   -d '{ "prefix":"ttt", "reference":80001, "adminUserName":"admin@ttt.example.com" }' \
  #                   http://localhost:8080/api/test/customers | jq
  #       hint: '| jq' is just for human-readable formatted JSON output
    shift
    jwtLogin
    doCurl --header "Content-Type: application/json" -X POST "$@"
    ;;
  "patch") ## HTTP PATCH, add curl options to specify the request body and the URL as last parameterparameter
  #       example: jwt-curl PATCH \
  #                   -d '{ "reference":80002 }' \
  #                   http://localhost:8080/api/test/customers/ae90ac2a-4728-4ca9-802e-a0d0108b2324 | jq
  #       hint: '| jq' is just for human-readable formatted JSON output
    shift
    jwtLogin
    doCurl --header "Content-Type: application/json" -X POST "$*"
    ;;

  # --- unknown command --------------------------------------------------------------------
  *)
    cat >&2 <<EOF
    unknown command: '$1'
    valid commands: help, login, logout, validate, get, post, patch, delete
EOF
    exit 1
    ;;
esac
