#!/usr/bin/env bash
set -euo pipefail

NAMESPACE="${NAMESPACE:-pcp-dev}"
POSTGRES_SERVICE="${POSTGRES_SERVICE:-pcp-postgres}"
CLUSTER_DOMAIN="${CLUSTER_DOMAIN:-altum.local}"
POSTGRES_IMAGE="${POSTGRES_IMAGE:-postgis/postgis:15-3.3}"
ADMIN_SECRET="${ADMIN_SECRET:-pcp-postgres-admin}"
ADMIN_PASSWORD_KEY="${ADMIN_PASSWORD_KEY:-POSTGRES_PASSWORD}"

POSTGRES_HOST="${POSTGRES_SERVICE}.${NAMESPACE}.svc.${CLUSTER_DOMAIN}"

usage() {
  cat <<EOF
Usage:
  $0 <db_name> [db_name...]

  $0 -f databases.txt

Examples:
  $0 pcp_satellite_catalog pcp_ballistics

  cat > databases.txt <<LIST
pcp_satellite_catalog
pcp_ballistics
pcp_requests
LIST

  $0 -f databases.txt

Environment overrides:
  NAMESPACE=pcp-dev
  POSTGRES_SERVICE=pcp-postgres
  CLUSTER_DOMAIN=altum.local
  POSTGRES_IMAGE=postgis/postgis:15-3.3
EOF
}

require_command() {
  if ! command -v "$1" >/dev/null 2>&1; then
    echo "ERROR: required command not found: $1" >&2
    exit 1
  fi
}

validate_db_name() {
  local db_name="$1"

  if [[ ! "$db_name" =~ ^[a-z][a-z0-9_]*$ ]]; then
    echo "ERROR: invalid database name: $db_name" >&2
    echo "Allowed format: lowercase letters, digits, underscore; must start with a letter." >&2
    exit 1
  fi
}

secret_name_for_db() {
  local db_name="$1"
  local dashed="${db_name//_/-}"
  echo "${dashed}-db"
}

user_name_for_db() {
  local db_name="$1"
  echo "${db_name}_user"
}

make_pod_name() {
  local prefix="$1"
  local db_name="$2"
  local safe="${db_name//_/-}"
  safe="${safe:0:35}"
  echo "${prefix}-${safe}-${RANDOM}${RANDOM}"
}

get_admin_password() {
  kubectl -n "$NAMESPACE" get secret "$ADMIN_SECRET" \
    -o jsonpath="{.data.${ADMIN_PASSWORD_KEY}}" | base64 -d
}

get_or_create_service_password() {
  local secret_name="$1"

  if kubectl -n "$NAMESPACE" get secret "$secret_name" >/dev/null 2>&1; then
    kubectl -n "$NAMESPACE" get secret "$secret_name" \
      -o jsonpath='{.data.SPRING_DATASOURCE_PASSWORD}' | base64 -d
  else
    openssl rand -base64 32 | tr -d '/+=' | head -c 32
  fi
}

create_database_and_user() {
  local db_name="$1"
  local db_user="$2"
  local db_password="$3"
  local admin_password="$4"

  local pod_name
  pod_name="$(make_pod_name "psql-create" "$db_name")"

  echo "Creating/updating database and user:"
  echo "  database: $db_name"
  echo "  user:     $db_user"

  kubectl -n "$NAMESPACE" run --rm -i "$pod_name" \
    --image="$POSTGRES_IMAGE" \
    --restart=Never \
    --env="PGPASSWORD=$admin_password" \
    --command -- psql \
      -h "$POSTGRES_HOST" \
      -U postgres \
      -d postgres <<SQL
DO \$\$
BEGIN
  IF NOT EXISTS (
    SELECT FROM pg_roles WHERE rolname = '$db_user'
  ) THEN
    CREATE ROLE "$db_user" LOGIN PASSWORD '$db_password';
  ELSE
    ALTER ROLE "$db_user" WITH LOGIN PASSWORD '$db_password';
  END IF;
END
\$\$;

SELECT 'CREATE DATABASE "$db_name" OWNER "$db_user"'
WHERE NOT EXISTS (
  SELECT FROM pg_database WHERE datname = '$db_name'
) \gexec

ALTER DATABASE "$db_name" OWNER TO "$db_user";
GRANT CONNECT ON DATABASE "$db_name" TO "$db_user";
SQL
}

enable_postgis() {
  local db_name="$1"
  local db_user="$2"
  local admin_password="$3"

  local pod_name
  pod_name="$(make_pod_name "psql-postgis" "$db_name")"

  echo "Enabling PostGIS:"
  echo "  database: $db_name"

  kubectl -n "$NAMESPACE" run --rm -i "$pod_name" \
    --image="$POSTGRES_IMAGE" \
    --restart=Never \
    --env="PGPASSWORD=$admin_password" \
    --command -- psql \
      -h "$POSTGRES_HOST" \
      -U postgres \
      -d "$db_name" <<SQL
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS postgis_topology;

ALTER SCHEMA public OWNER TO "$db_user";
GRANT USAGE, CREATE ON SCHEMA public TO "$db_user";
SQL
}

create_or_update_secret() {
  local db_name="$1"
  local db_user="$2"
  local db_password="$3"
  local secret_name="$4"

  echo "Creating/updating Kubernetes Secret:"
  echo "  secret: $secret_name"

  kubectl -n "$NAMESPACE" create secret generic "$secret_name" \
    --from-literal=SPRING_DATASOURCE_USERNAME="$db_user" \
    --from-literal=SPRING_DATASOURCE_PASSWORD="$db_password" \
    --dry-run=client -o yaml | kubectl apply -f -
}

check_database() {
  local db_name="$1"
  local db_user="$2"
  local db_password="$3"

  local pod_name
  pod_name="$(make_pod_name "psql-check" "$db_name")"

  echo "Checking database:"
  echo "  database: $db_name"
  echo "  user:     $db_user"

  kubectl -n "$NAMESPACE" run --rm -i "$pod_name" \
    --image="$POSTGRES_IMAGE" \
    --restart=Never \
    --env="PGPASSWORD=$db_password" \
    --command -- psql \
      -h "$POSTGRES_HOST" \
      -U "$db_user" \
      -d "$db_name" \
      -c 'select current_database(), current_user, postgis_full_version();'
}

read_db_list() {
  if [[ $# -eq 0 ]]; then
    usage
    exit 1
  fi

  if [[ "$1" == "-h" || "$1" == "--help" ]]; then
    usage
    exit 0
  fi

  if [[ "$1" == "-f" || "$1" == "--file" ]]; then
    if [[ $# -ne 2 ]]; then
      echo "ERROR: file path is required after $1" >&2
      exit 1
    fi

    local file="$2"

    if [[ ! -f "$file" ]]; then
      echo "ERROR: file not found: $file" >&2
      exit 1
    fi

    grep -vE '^\s*($|#)' "$file"
  else
    printf '%s\n' "$@"
  fi
}

main() {
  require_command kubectl
  require_command openssl
  require_command base64

  echo "Namespace:       $NAMESPACE"
  echo "Postgres host:   $POSTGRES_HOST"
  echo "Postgres image:  $POSTGRES_IMAGE"
  echo "Admin secret:    $ADMIN_SECRET"
  echo

  local admin_password
  admin_password="$(get_admin_password)"

  mapfile -t databases < <(read_db_list "$@")

  if [[ "${#databases[@]}" -eq 0 ]]; then
    echo "ERROR: database list is empty" >&2
    exit 1
  fi

  for db_name in "${databases[@]}"; do
    validate_db_name "$db_name"

    local db_user
    local secret_name
    local db_password
    local jdbc_url

    db_user="$(user_name_for_db "$db_name")"
    secret_name="$(secret_name_for_db "$db_name")"
    db_password="$(get_or_create_service_password "$secret_name")"
    jdbc_url="jdbc:postgresql://${POSTGRES_HOST}:5432/${db_name}"

    echo
    echo "============================================================"
    echo "Processing database: $db_name"
    echo "============================================================"

    create_database_and_user "$db_name" "$db_user" "$db_password" "$admin_password"
    enable_postgis "$db_name" "$db_user" "$admin_password"
    create_or_update_secret "$db_name" "$db_user" "$db_password" "$secret_name"
    check_database "$db_name" "$db_user" "$db_password"

    echo
    echo "Done:"
    echo "  database: $db_name"
    echo "  user:     $db_user"
    echo "  secret:   $secret_name"
    echo "  JDBC URL: $jdbc_url"
  done

  echo
  echo "All databases processed successfully."
}

main "$@"
