#!/bin/sh
set -eu

ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
SERVER_ENV="${TELEBIT_SERVER_ENV:-$ROOT_DIR/.env}"
CLIENT_ENV="${TELEBIT_CLIENT_ENV:-$ROOT_DIR/telebit-client.env}"
CLIENT_ENV_DIR="${TELEBIT_CLIENT_ENV_DIR:-$ROOT_DIR/.telebit-clients}"
TELEBIT_BIN="${TELEBIT_BIN:-$ROOT_DIR/bin/telebit}"
SIGNJWT_BIN="${SIGNJWT_BIN:-$ROOT_DIR/bin/signjwt}"

usage() {
  cat <<'USAGE'
Usage:
  telebitctl build [os] [arch]
  telebitctl bind <slug>
  telebitctl release <slug>
  telebitctl auth refresh [slug]
  telebitctl up <slug> <local-port>

Examples:
  ./telebitctl build
  ./telebitctl bind test
  ./telebitctl up test 80
  ./telebitctl auth refresh test
  ./telebitctl release test
USAGE
}

die() {
  printf '%s\n' "$*" >&2
  exit 1
}

load_server_env() {
  [ -f "$SERVER_ENV" ] || die "Missing $SERVER_ENV. Copy .env.example to .env and set DOMAIN, TUNNEL_DOMAIN, and SECRET."
  set -a
  # shellcheck disable=SC1090
  . "$SERVER_ENV"
  set +a

  : "${DOMAIN:?DOMAIN is required in $SERVER_ENV}"
  : "${TUNNEL_DOMAIN:?TUNNEL_DOMAIN is required in $SERVER_ENV}"
  : "${SECRET:?SECRET is required in $SERVER_ENV}"

  VENDOR_ID="${VENDOR_ID:-renman-dev}"
  MGMT_URL="${MGMT_URL:-https://$TUNNEL_DOMAIN/api}"
}

need_bin() {
  [ -x "$1" ] || die "Missing executable $1. Run: ./telebitctl build"
}

admin_token() {
  need_bin "$SIGNJWT_BIN"
  "$SIGNJWT_BIN" \
    --expires-in 5m \
    --vendor-id "$VENDOR_ID" \
    --secret "$SECRET" \
    --machine-ppid "$SECRET" | awk '/^eyJ/ {print; exit}'
}

current_slug() {
  [ -f "$CLIENT_ENV" ] || return 1
  sed -n "s#^LOCALS=https:\\([^:]*\\):.*#\\1#p" "$CLIENT_ENV" | sed "s#\\.$DOMAIN\$##" | sed -n '1p'
}

current_port() {
  [ -f "$CLIENT_ENV" ] || return 1
  sed -n 's#^LOCALS=https:[^:]*:\([0-9][0-9]*\).*#\1#p' "$CLIENT_ENV" | sed -n '1p'
}

safe_slug() {
  case "$1" in
    ""|*/*|*..*|.*|*-|-*|*[^A-Za-z0-9-]*)
      die "Invalid slug: $1"
      ;;
  esac
}

client_env_path() {
  slug="$1"
  safe_slug "$slug"
  printf '%s/%s.env\n' "$CLIENT_ENV_DIR" "$slug"
}

env_port() {
  env_file="$1"
  [ -f "$env_file" ] || return 1
  sed -n 's#^LOCALS=https:[^:]*:\([0-9][0-9]*\).*#\1#p' "$env_file" | sed -n '1p'
}

write_client_env() {
  slug="$1"
  shared_key="$2"
  port="${3:-}"
  env_file="${4:-$(client_env_path "$slug")}"
  port="${port:-$(env_port "$env_file" || current_port || true)}"
  port="${port:-80}"

  mkdir -p "$(dirname "$env_file")"
  cat > "$env_file" <<EOF
TUNNEL_RELAY_URL=wss://$TUNNEL_DOMAIN/ws
AUTH_URL=https://$TUNNEL_DOMAIN/api
ACME_HTTP_01_RELAY_URL=https://$TUNNEL_DOMAIN/api/acme-relay
ACME_RELAY_URL=https://$TUNNEL_DOMAIN/api/acme-relay
ACME_AGREE=true

VENDOR_ID=$VENDOR_ID
SECRET=$shared_key

LOCALS=https:$slug.$DOMAIN:$port
EOF

  cp "$env_file" "$CLIENT_ENV"
  printf 'Updated %s\n' "$env_file"
}

client_key() {
  env_file="${1:-$CLIENT_ENV}"
  [ -f "$env_file" ] || return 1
  sed -n 's/^SECRET=//p' "$env_file" | sed -n '1p'
}

create_device() {
  slug="$1"
  token="$2"
  response=$(curl -fsS -X POST "$MGMT_URL/devices" \
    -H "Authorization: Bearer $token" \
    -H "Content-Type: application/json" \
    -d "{ \"slug\": \"$slug\" }")

  shared_key=$(printf '%s\n' "$response" | sed -n 's/.*"shared_key":"\([^"]*\)".*/\1/p')
  [ -n "$shared_key" ] || die "Could not parse shared_key from response: $response"
  printf '%s\n' "$shared_key"
}

bind_slug() {
  load_server_env
  slug="${1:?bind requires a slug}"
  env_file=$(client_env_path "$slug")
  token=$(admin_token)
  shared_key=$(create_device "$slug" "$token")
  write_client_env "$slug" "$shared_key" "$(env_port "$env_file" || current_port || true)" "$env_file"
  printf 'Bound %s.%s\n' "$slug" "$DOMAIN"
}

release_slug() {
  load_server_env
  slug="${1:?release requires a slug}"
  env_file=$(client_env_path "$slug")
  token=$(admin_token)
  delete_device "$slug" "$token" true
  rm -f "$env_file"
  if [ "$(current_slug || true)" = "$slug" ]; then
    rm -f "$CLIENT_ENV"
  fi
}

delete_device() {
  slug="$1"
  token="$2"
  strict="${3:-false}"
  tmp=$(mktemp)
  code=$(curl -sS -o "$tmp" -w '%{http_code}' -X DELETE "$MGMT_URL/devices/$slug" \
    -H "Authorization: Bearer $token")
  body=$(cat "$tmp")
  rm -f "$tmp"

  case "$code:$body" in
    200:*|500:*E_NOT_FOUND*)
      printf 'Released %s.%s\n' "$slug" "$DOMAIN"
      ;;
    *)
      [ "$strict" = "true" ] || return 0
      printf '%s\n' "$body" >&2
      die "Release failed with HTTP $code"
      ;;
  esac
}

refresh_auth() {
  load_server_env
  slug="${1:-$(current_slug || true)}"
  slug="${slug:-test}"
  env_file=$(client_env_path "$slug")
  token=$(admin_token)
  delete_device "$slug" "$token" false >/dev/null 2>&1 || true
  shared_key=$(create_device "$slug" "$token")
  write_client_env "$slug" "$shared_key" "$(env_port "$env_file" || current_port || true)" "$env_file"
  printf 'Bound %s.%s\n' "$slug" "$DOMAIN"
}

up_client() {
  load_server_env
  need_bin "$TELEBIT_BIN"
  slug="${1:?up requires a slug}"
  port="${2:?up requires a local port}"
  env_file=$(client_env_path "$slug")
  key="$(client_key "$env_file" || true)"

  if [ -z "$key" ]; then
    token=$(admin_token)
    key=$(create_device "$slug" "$token")
  fi

  write_client_env "$slug" "$key" "$port" "$env_file" >/dev/null
  unset SECRET DOMAIN TUNNEL_DOMAIN MGMT_URL POSTGRES_PASSWORD TELEBIT_PORT MGMT_PORT VENDOR_ID
  cd "$HOME"
  exec "$TELEBIT_BIN" --env "$env_file"
}

build_client() {
  os="${1:-}"
  arch="${2:-}"

  if [ -z "$os" ]; then
    case "$(uname -s)" in
      Darwin) os=darwin ;;
      Linux) os=linux ;;
      *) die "Unsupported OS. Pass os and arch explicitly." ;;
    esac
  fi

  if [ -z "$arch" ]; then
    case "$(uname -m)" in
      arm64|aarch64) arch=arm64 ;;
      x86_64|amd64) arch=amd64 ;;
      *) die "Unsupported arch. Pass os and arch explicitly." ;;
    esac
  fi

  mkdir -p "$ROOT_DIR/bin"
  docker build \
    -f "$ROOT_DIR/Dockerfile.client" \
    --build-arg "TELEBIT_REF=${TELEBIT_REF:-master}" \
    --build-arg "TARGETOS=$os" \
    --build-arg "TARGETARCH=$arch" \
    --output "type=local,dest=$ROOT_DIR/bin" \
    "$ROOT_DIR"

  chmod +x "$ROOT_DIR/bin/telebit" "$ROOT_DIR/bin/signjwt"
  printf 'Built bin/telebit and bin/signjwt for %s/%s\n' "$os" "$arch"
}

cmd="${1:-}"
case "$cmd" in
  build)
    shift
    build_client "$@"
    ;;
  bind)
    shift
    bind_slug "$@"
    ;;
  release)
    shift
    release_slug "$@"
    ;;
  auth)
    shift
    sub="${1:-}"
    [ "$sub" = "refresh" ] || die "Unknown auth command: ${sub:-}. Expected: auth refresh [slug]"
    shift || true
    refresh_auth "$@"
    ;;
  up)
    shift
    up_client "$@"
    ;;
  -h|--help|help|"")
    usage
    ;;
  *)
    usage
    exit 1
    ;;
esac
