#!/bin/bash
# This file is part of GNU TALER.
# Copyright (C) 2023-2024 Taler Systems SA
#
# TALER is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free Software
# Foundation; either version 2.1, or (at your option) any later version.
#
# TALER is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along with
# TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
#
# @author Christian Grothoff
# @author Florian Dold

# Error checking on
set -eu

# 1 is true, 0 is false
RESET_DB=0
FORCE_PERMS=0
SKIP_INIT=0
SKIP_NEXUS=0
SKIP_BANK=0
NEXUS_DBUSER="libeufin-nexus"
BANK_DBUSER="libeufin-bank"
NEXUS_CFGFILE="/etc/libeufin/libeufin-nexus.conf"
BANK_CFGFILE="/etc/libeufin/libeufin-bank.conf"

function exit_fail() {
  echo "$@" >&2
  exit 1
}

VALID_ARGS=`getopt -o b:hrn:psu:v: -l bank-config:,help,nexus-config:,reset,skip,permissions,nexus-user:,bank-user:,only-nexus,only-bank -n 'libeufin-dbconfig' -- "$@"`

if [ $? != 0 ] ;
then
  exit 1 ;
fi
eval set -- "$VALID_ARGS"

function usage {
   cat - <<EOF
libeufin-dbconfig
Setup databases for libeufin components.
Arguments mandatory for long options are also mandatory for short options.
  -b, --bank-config=FILE      use CONFIGFILE for the Bank configuration file (default: $BANK_CFGFILE)
  -h, --help                  print this help
  -r, --reset                 reset database (dangerous)
  -s, --skip                  skip database initialization
  -n, --nexus-config=FILE     use CONFIGFILE for the Nexus configuration file (default: $NEXUS_CFGFILE)
  -p, --permissions           force permission setup even without database initialization
  -u, --nexus-user=NEXUS_USER libeufin-nexus to be run by USER (default: $NEXUS_DBUSER)
  -v, --bank-user=BANK_USER   libeufin-bank to be run by USER (default: $BANK_DBUSER)
  --only-nexus                run only for libeufin-nexus db
  --only-bank                 run only for libeufin-bank db
EOF
}

# Parse command-line options
while true; do
  case "$1" in
  -b | --bank-config) BANK_CFGFILE="$1"; shift 2 ;;
  -h | --help) usage; exit 0 ;;
  -r | --reset) RESET_DB="1"; shift ;;
  -s | --skip) SKIP_INIT="1"; shift ;;
  -n | --nexus-config) NEXUS_CFGFILE="$1"; shift 2 ;;
  -p | --permissions) FORCE_PERMS="1"; shift ;;
  --only-nexus) SKIP_BANK="1"; shift ;;
  --only-bank) SKIP_NEXUS="1"; shift ;;
  -u | --nexus-user) NEXUS_DBUSER="$1"; shift 2 ;;
  -v | --bank-user) BANK_DBUSER="$1"; shift 2 ;;
  --) shift; break ;;
  *) usage; exit 1 ;;
  esac
done

if ! id postgres >/dev/null; then
  exit_fail "Could not find 'postgres' user. Please install Postgresql first"
fi

if [ "$(id -u)" -ne 0 ]; then
  exit_fail "This script must be run as root"
fi

# Check tools availability if they are going to be used
function check_availability {
  if ! $1 --help 1>/dev/null; then
    exit_fail "Required '$1' not found. Please fix your installation."
  fi
  echo $(which $1)
}
if [ 0 = "$SKIP_INIT" ]; then
  if [ 0 = "$SKIP_BANK" ]; then
    BANK_DBINIT=$(check_availability libeufin-bank-dbinit)
  fi
  if [ 0 = "$SKIP_NEXUS" ]; then
    NEXUS_DBINIT=$(check_availability libeufin-nexus-dbinit)
  fi
fi

# Check OS users exist
function check_os_user {
  if ! id "$1" >/dev/null; then
    exit_fail "Could not find '$1' user. Cannot continue"
  fi
}
if [ 0 = "$SKIP_BANK" ]; then check_os_user "$BANK_DBUSER"; fi
if [ 0 = "$SKIP_NEXUS" ]; then check_os_user "$NEXUS_DBUSER"; fi

# Create DB users matching OS users names
function create_db_user {
  echo "Setting up database user '$1'." 1>&2
  if ! sudo -i -u postgres createuser "$1" 2>/dev/null; then
    echo "Database user '$1' already existed. Continuing anyway." 1>&2
  fi
}
if [ 0 = "$SKIP_BANK" ]; then create_db_user "$BANK_DBUSER"; fi
if [ 0 = "$SKIP_NEXUS" ]; then create_db_user "$NEXUS_DBUSER"; fi

# Check database name
function get_db_name {
  if ! echo "$1" | grep "postgres://" >/dev/null; then
    exit_fail "Invalid libeufin-$2 database configuration value '$1'."
  fi

  # Remove URI, host and query from postgres URI.
  echo $(echo "$1" | sed -e 's|postgres://.*/||' -e 's|?.*||')
}
if [ 0 = "$SKIP_BANK" ]; then
  BANK_DBNAME=$(get_db_name $(libeufin-bank config get libeufin-bankdb-postgres CONFIG) "bank")
fi
if [ 0 = "$SKIP_NEXUS" ]; then
  NEXUS_DBNAME=$(get_db_name $(libeufin-nexus config get nexus-postgres CONFIG 2> /dev/null | libeufin-nexus config get libeufin-nexusdb-postgres CONFIG) "nexus")
fi

# If using both components they must use the same database
if [[ 0 = "$SKIP_BANK" && 0 = "$SKIP_NEXUS" && "$NEXUS_DBNAME" != "$BANK_DBNAME" ]]; then
  exit_fail "Database names for libeufin-bank and libeufin-nexus must match ($NEXUS_DBNAME vs $BANK_DBNAME)"
fi

if [ 0 = "$SKIP_BANK" ]; then
  DBNAME=$BANK_DBNAME
  DBUSER=$BANK_DBUSER
else
  DBNAME=$NEXUS_DBNAME
  DBUSER=$NEXUS_DBUSER
fi

if sudo -i -u postgres psql "$DBNAME" </dev/null 2>/dev/null; then
  if [ 1 = "$RESET_DB" ]; then
    echo "Deleting existing database '$DBNAME'." 1>&2
    if ! sudo -i -u postgres dropdb "$DBNAME"; then
      exit_fail "Failed to delete existing database '$DBNAME'"
    fi
    DO_CREATE=1
  else
    echo "Database '$DBNAME' already exists, continuing anyway."
    DO_CREATE=0
  fi
else
  DO_CREATE=1
fi

if [ 1 = "$DO_CREATE" ]; then
  echo "Creating database '$DBNAME'." 1>&2
  if ! sudo -i -u postgres createdb -O "$DBUSER" "$DBNAME"; then
    exit_fail "Failed to create database '$DBNAME'"
  fi
fi

# TODO: add a command to only init the _v schema for a simpler init logic

function grant_db_access {
  if ! echo "GRANT ALL PRIVILEGES ON DATABASE $DBNAME TO \"$1\"" |
    sudo -i -u postgres psql "$DBNAME"; then
    exit_fail "Failed to grant access to database '$DBNAME' to '$1'."
  fi
}
function grant_schema_access {
  if ! echo "GRANT USAGE ON SCHEMA $2 TO \"$1\"" |
    sudo -i -u postgres psql "$DBNAME"; then
    exit_fail "Failed to grant usage privilege on schema '$2' to '$1'."
  fi
  if ! echo "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA $2 TO \"$1\"" |
    sudo -i -u postgres psql "$DBNAME"; then
    exit_fail "Failed to grant access to schema '$2' to '$1'."
  fi
}

# Init database with one of the users to create the _v schema
if [ 0 = "$SKIP_INIT" ]; then
  if [ 0 = "$SKIP_BANK" ]; then
    echo "Initializing database '$DBNAME' for libeufin-bank." 1>&2
    sudo -u "$BANK_DBUSER" "$BANK_DBINIT" -c "$BANK_CFGFILE"
  else
    echo "Initializing database '$DBNAME' for libeufin-nexus." 1>&2
    sudo -u "$NEXUS_DBUSER" "$NEXUS_DBINIT" -c "$NEXUS_CFGFILE"
  fi
fi

# nexus permission to access db and _v schema if bank init the database
if [[ 0 = "$SKIP_INIT" || 1 = "$FORCE_PERMS" ]] && [[ 0 = "$SKIP_BANK"  &&  0 = "$SKIP_NEXUS" ]]; then
  echo "Setting postgres permissions for '$NEXUS_DBUSER'" 1>&2
  grant_db_access "$NEXUS_DBUSER"
  grant_schema_access "$NEXUS_DBUSER" "_v"
  grant_schema_access "$NEXUS_DBUSER" "libeufin_bank"
fi

# DB initialization for nexus if both component are setup
if [[ 0 = "$SKIP_INIT" && 0 = "$SKIP_BANK" && 0 = "$SKIP_NEXUS" ]]; then
  echo "Initializing database '$DBNAME' for libeufin-nexus." 1>&2
  sudo -u "$NEXUS_DBUSER" "$NEXUS_DBINIT" -c "$NEXUS_CFGFILE"
fi

# bank permission to access nexus schema if both component are setup
if [[ 0 = "$SKIP_INIT" || 1 = "$FORCE_PERMS" ]] && [[ 0 = "$SKIP_BANK"  &&  0 = "$SKIP_NEXUS" ]]; then
  echo "Setting postgres permissions for '$BANK_DBUSER'" 1>&2
  grant_schema_access "$BANK_DBUSER" "libeufin_nexus"
fi

echo "Database configuration finished." 1>&2
