#!/bin/sh

set -e
set -a # automatically export all variables
. ./.env.development
if [ -f .env.local ]; then
  . ./.env.local
fi

logFilter() {
  grep -v --line-buffered --color=never 'POST /api/graphql .*200'
}

docker__compose() {
  if [ ! -x "$(command -v docker)" ]; then
    echo "docker compose is not installed"
    echo "installation instructions are here: https://docs.docker.com/desktop/"
    exit 0
  fi

  ENV_LOCAL=
  if [ -f .env.local ]; then
    ENV_LOCAL='--env-file .env.local'
  fi

  CURRENT_UID=$(id -u) CURRENT_GID=$(id -g) command docker compose --env-file .env.development $ENV_LOCAL "$@"
}

docker__exec() {
  if [ ! -x "$(command -v docker)" ]; then
    echo "docker is not installed"
    echo "installation instructions are here: https://docs.docker.com/desktop/"
    exit 0
  fi

  DOCKER_CLI_HINTS=false command docker exec -i "$@"
}

sndev__start() {
  shift

  if [ $# -eq 0 ]; then
    docker__compose up --build
    exit 0
  fi

  docker__compose up "$@"
}

sndev__help_start() {
help="
start the sndev env

USAGE
  $ sndev start [OPTIONS] [SERVICE...]

OPTIONS"

  echo "$help"
  docker__compose up --help | awk '/Options:/{y=1;next}y'
}

sndev__stop() {
  shift
  docker__compose down "$@"
}

sndev__help_stop() {
help="
stop the sndev env

USAGE
  $ sndev stop [OPTIONS] [SERVICE...]

OPTIONS"

  echo "$help"
  docker__compose down --help | awk '/Options:/{y=1;next}y'
}

sndev__open() {
  shift
  service=$(docker__compose ps $1 --format '{{.Label "CONNECT"}}')
  if [ -z "$service" ]; then
    echo "no url found for $1"
    exit 1
  fi
  service="http://$service"

  echo "opening $1 ... $service"
  if [ "$(uname)" = "Darwin" ]; then
    open $service
  elif [ "$(uname)" = "Linux" ]; then
    xdg-open $service
  elif [ "$(uname)" = "Windows_NT" ]; then
    start $service
  fi
}

sndev__help_open() {
help="
open a container's url if it has one

USAGE
  $ sndev open SERVICE

OPTIONS
  no options currently exist
"

  echo "$help"
}

sndev__restart() {
  shift
  docker__compose restart "$@"
}

sndev__help_restart() {
help="
restart the sndev env

USAGE
  $ sndev restart [OPTIONS] [SERVICE...]

OPTIONS"

  echo "$help"
  docker__compose restart --help | awk '/Options:/{y=1;next}y'
}

sndev__logs() {
  shift
  if [ $# -eq 1 ]; then
    docker__compose logs -t --tail=1000 -f "$@" | logFilter
    exit 0
  fi

  docker__compose logs "$@"
}

sndev__help_logs() {
help="
get logs from sndev env

USAGE
  $ sndev logs [OPTIONS] [SERVICE...]

OPTIONS"

  echo "$help"
  docker__compose logs --help | awk '/Options:/{y=1;next}y'
}

sndev__status() {
  shift
  if [ $# -eq 0 ]; then
    docker__compose ps -a --format 'table {{.Service}}\t{{.State}}\t{{.Status}}\t{{.Label "CONNECT"}}'
    exit 0
  fi
  docker__compose ps "$@"
}

sndev__help_status() {
help="
show container status of sndev env

USAGE
  $ sndev status [OPTIONS] [SERVICE...]

OPTIONS"

  echo "$help"
  docker__compose ps --help | awk '/Options:/{y=1;next}y'
}

sndev__delete() {
  printf "this deletes containers, volumes, and orphans - are you sure? [y/N] "
  read -r answer
  if [ "$answer" = "y" ]; then
    docker__compose down --volumes --remove-orphans
  else
    echo "delete cancelled"
  fi
}

sndev__help_delete() {
help="
remove orphans and volumes from sndev env
equivalent to sndev stop --volumes --remove-orphans

USAGE
  $ sndev delete
"

  echo "$help"
}

sndev__set_balance() {
  shift

  if [ -z "$1" ]; then
    echo "NYM argument required"
    sndev__help_set_balance
    exit 1
  fi

  if [ -z "$2" ]; then
    echo "MSATS argument required"
    sndev__help_set_balance
    exit 2
  fi

  if ! echo "$2" | grep -qE "^[0-9]+$"; then
   echo "MSATS argument is not a positive integer"
   sndev__help_set_balance
   exit 3
  fi

  docker__exec db psql -U sn -d stackernews -q <<EOF
    UPDATE users set msats = $2 where name = '$1';
EOF
}

sndev__help_set_balance() {
  help="
set the balance of a nym

USAGE
  $ sndev set_balance NYM MSATS

  NYM - the name of the user you want to set the balance of
  MSATS - the amount of millisatoshis to set the account to. Must be >= 0
"

  echo "$help"
}

sndev__fund() {
  shift
  if [ "$1" = "--cln" ]; then
    shift
    sndev__cli -t cln pay "$@"
  else
    sndev__cli -t lnd payinvoice "$@"
  fi
}

sndev__help_fund() {
help="
pay a bolt11 for funding

USAGE
  $ sndev fund BOLT11 [OPTIONS]
  $ sndev fund --cln BOLT11

OPTIONS"

  echo "$help"
  sndev__cli lnd payinvoice -h | awk '/OPTIONS:/{y=1;next}y' | awk '!/^[\t ]+--pay_req value/'
}

sndev__withdraw() {
  shift
  if [ "$1" = "--cln" ]; then
    shift
    label=$(date +%s)
    sndev__cli -t cln invoice "$1"sats "$label" sndev | jq -j '.bolt11'; echo
  else
    sndev__cli lnd addinvoice --amt "$@" | jq -j '.payment_request'; echo
  fi
}

sndev__help_withdraw() {
  help="
create a bolt11 for withdrawal

USAGE
  $ sndev withdraw SATS [OPTIONS]
  $ sndev withdraw --cln SATS

OPTIONS"

  echo "$help"
  sndev__cli lnd addinvoice -h | awk '/OPTIONS:/{y=1;next}y' | awk '!/^[\t ]+(--amt|--amt_msat) value/'
}

sndev__psql() {
  shift
  docker__exec -t db psql "$@" -U sn -d stackernews
}

sndev__help_psql() {
  help="
open psql on db

USAGE
  $ sndev psql [OPTIONS]

OPTIONS"

  echo "$help"
  docker__exec db psql --help | awk '/General options:/{y=1;next}y' | sed -n '/Connection options:/q;p' |
    awk '!/^([\t ]+-l, --list)|([\t ]+-d, --dbname)|([\t ]+-\?, --help)|([\t ]--help=)/'
}

sndev__prisma() {
  shift
  docker__exec -t -u apprunner app npx prisma "$@"
}

sndev__help_prisma() {
  help="
run prisma commands

USAGE
  $ sndev prisma [COMMAND]

COMMANDS"

  echo "$help"
  sndev__prisma --help | awk '/Commands/{y=1;next}y' | awk '!/^([\t ]+init)|([\t ]+studio)/' | sed -n '/Flags/q;p'
}

sndev__lint() {
  shift
  docker__exec -t -u apprunner app npm run lint
}

sndev__help_lint() {
  help="
run linters

USAGE
  $ sndev lint
"

  echo "$help"
}

sndev__test() {
  shift

  args=""
  if [ $# -gt 0 ]; then
    args="-- $@"
  fi

  docker__exec -t -u apprunner app npm run test $args
}

sndev__help_test() {
  help="
run tests

USAGE
  $ sndev test [OPTIONS]

OPTIONS"

  echo "$help"
  docker__exec -u apprunner app npm run test -- --help | awk '/Options:/{y=1;next}y'
}

sndev__compose() {
  shift
  docker__compose "$@"
}

sndev__help_compose() {
  docker__compose --help
}

__sndev__pr_track() {
  json=$(curl -fsSH "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/stackernews/stacker.news/pulls/$1")
  case $(git config --get remote.origin.url) in
    "http"*) url=$(echo "$json" | grep -e '"clone_url"' | head -n1 | sed -e 's/^.*"clone_url":[[:space:]]*"//; s/",[[:space:]]*$//') ;;
    *)       url=$(echo "$json" | grep -e '"ssh_url"' | head -n1 | sed -e 's/^.*"ssh_url":[[:space:]]*"//; s/",[[:space:]]*$//') ;;
  esac

  push=$(git remote -v | grep -e "$url .*push" | head -n1) || true
  if [ -n "$push" ]; then
    remote=$(echo "$push" | cut -f 1)
  else
    remote=$(echo "$json" | grep -e '"login"' | head -n1 | sed -e 's/^.*"login":[[:space:]]*"//; s/",[[:space:]]*$//')
    git remote remove "$remote" 1>/dev/null 2>&1 || true
    git remote add "$remote" "$url"
  fi

  ref=$(echo "$json" | grep -e '"ref"' | head -n1 | sed -e 's/^.*"ref":[[:space:]]*"//; s/",[[:space:]]*$//')
  git fetch "$remote" "$ref"
  git checkout -t -b "pr/$1" "$remote/$ref"
  git config --local "remote.$remote.push" pr/$1:$ref
  exit 0
}

__sndev__pr_detach() {
  refspec="+refs/pull/$1/head:refs/remotes/pr/$1"
  case $(git config --get remote.origin.url) in
      "http"*) git fetch https://github.com/stackernews/stacker.news.git "$refspec" ;;
    *)       git fetch git@github.com:stackernews/stacker.news.git "$refspec" ;;
  esac
  git checkout "pr/$1"
  exit 0
}

sndev__pr() {
  shift

  case $1 in
    -t|--track)
      call "__sndev__pr_track" "$2" ;;
    *)
      call "__sndev__pr_detach" "$1" ;;
  esac
}

sndev__help_pr() {
  help="
fetch and checkout a pr

USAGE
  $ sndev pr [OPTIONS] PR_NUMBER

OPTIONS
  -t, --track   track the pr in a new branch, creating a remote if necessary
                defaults to checking out the pr in a detached state
"

  echo "$help"
}

sndev__login() {
  shift
  if [ -z "$1" ]; then
    echo "NYM argument required"
    sndev__help_login
    exit 1
  fi
  # hardcode token for which is the hex digest of the sha256 of
  # "SNDEV-TOKEN3_0W_PhDRZVanbeJsZZGIEljexkKoGbL6qGIqSwTjjI"
  # next-auth concats the token with the secret from env and then sha256's it
  token="d5fce54babffcb070c39f78d947761fd9ec37647fafcecb9734a3085a78e5c5e"
  salt="202c90943c313b829e65e3f29164fb5dd7ea3370d7262c4159691c2f6493bb8b"
  # upsert user with nym and nym@sndev.team
  email="$1@sndev.team"
  docker__exec db psql -U sn -d stackernews -q <<EOF
    INSERT INTO users (name) VALUES ('$1') ON CONFLICT DO NOTHING;
    UPDATE users SET email = '$email', "emailHash" = encode(digest(LOWER('$email')||'$salt', 'sha256'), 'hex') WHERE name = '$1';
    INSERT INTO verification_requests (identifier, token, expires)
      VALUES ('$email', '$token', NOW() + INTERVAL '1 day')
      ON CONFLICT (token) DO UPDATE
      SET identifier = '$email', expires = NOW() + INTERVAL '1 day';
EOF

  echo
  echo "open url in browser"
  echo "http://localhost:3000/api/auth/callback/email?token=SNDEV-TOKEN&email=$1%40sndev.team"
  echo
}

sndev__help_login() {
  help="
login as a nym

USAGE
  $ sndev login NYM
"

  echo "$help"
}

sndev__onion() {
  shift
  tordir=$(docker__compose ps $1 --format '{{.Label "TORDIR"}}')
  if [ -z "$tordir" ]; then
    echo "no TORDIR label found for $1"
    exit 1
  fi
  onion=$(docker__exec $1 cat $tordir/hidden_service/hostname | tr -d '[:space:]')
  echo "$onion"
}

sndev__help_onion() {
  help="
get the onion address of a service

USAGE
  $ sndev onion SERVICE
"

  echo "$help"
}

sndev__cert() {
  shift
  certdir=$(docker__compose ps $1 --format '{{.Label "CERTDIR"}}')
  if [ -z "$certdir" ]; then
    echo "no CERTDIR label found for $1"
    exit 1
  fi
  docker__exec $1 cat $certdir/tls.cert | base64
}

sndev__help_cert() {
  help="
get the tls cert of a service

USAGE
  $ sndev cert SERVICE
"

  echo "$help"
}

sndev__cli() {
  t=$1

  if [ "$t" = "-t" ]; then
    shift
  else
    t=""
  fi

  if [ "$1" = "cli" ]; then
    shift
  fi

  if [ -z "$1" ]; then
    echo "SERVICE required"
    sndev__help_cli
    exit 1
  fi

  service=$1
  cli=$(docker__compose ps $service --format '{{.Label "CLI"}}')
  cli_user=$(docker__compose ps $service --format '{{.Label "CLI_USER"}}')
  cli_args=$(docker__compose ps $service --format '{{.Label "CLI_ARGS"}}')

  if [ -z "$cli" ]; then
    echo "no CLI label found for $service"
    exit 1
  fi

  shift

  if [ -n "$cli_user" ]; then
    cli_user="-u $cli_user"
  fi

  docker__exec $t $cli_user $service $cli $cli_args "$@"
}

sndev__help_cli() {
  help="
run a cli command on a service

USAGE
  $ sndev cli SERVICE [COMMAND [ARGS]]
"

  echo "$help"
}

sndev__help() {
    if [ $# -eq 2 ]; then
      call "sndev__$1_$2" "$@"
      exit 0
    fi

help="
                            888
                            888
                            888
      .d8888b  88888b.  .d88888  .d88b.  888  888
     88K      888 '88b d88' 888 d8P  Y8b 888  888
     'Y8888b. 888  888 888  888 88888888 Y88  88P
          X88 888  888 Y88b 888 Y8b.      Y8bd8P
      88888P' 888  888  'Y88888  'Y8888    Y88P

manages a docker based stacker news development environment

USAGE
  $ sndev [COMMAND]
  $ sndev help [COMMAND]

COMMANDS
  help                    show help

  env:
    start                 start env
    stop                  stop env
    restart               restart env
    status                status of env
    logs                  logs from env
    delete                delete env

  sn:
    login                 login as a nym
    set_balance           set the balance of a nym

  lightning:
    fund                   pay a bolt11 for funding
    withdraw               create a bolt11 for withdrawal

  db:
    psql                   open psql on db
    prisma                 run prisma commands

  dev:
    pr                     fetch and checkout a pr
    lint                   run linters
    test                   run tests

  other:
    cli                    service cli passthrough
    open                   open service GUI in browser
    onion                  service onion address
    cert                   service tls cert
    compose                docker compose passthrough
"
  echo "$help"
}

call() {
    func=$1
    if type "$func" 1>/dev/null 2>&1; then
        # if it's sndev COMMAND help, then call help for that command
        case $3 in
          -h|--help|help)
          call "sndev__help_$2"
          exit 0
          ;;
        esac
        shift # remove func from args
        "$func" "$@"  # invoke our named function w/ all remaining arguments
    else
        # if it's sndev -h COMMAND, then call help for that command
        case $2 in
          -h|--help)
          call "sndev__help_$3"
          exit 0
          ;;
        esac
        sndev__help
        exit 1
    fi
}

call "sndev__$1" "$@"