#!/bin/bash
# Shell script to launch Taler components
# and Anastasis providers for local test
# using TESTKUDOS.

set -eu

# Exit, with status code "skip" (no 'real' failure)
function exit_skip() {
    echo " SKIP: $1"
    exit 77
}

# Exit, with error message (hard failure)
function exit_fail() {
    echo " FAIL: $1"
    exit 1
}

# Cleanup to run whenever we exit
function cleanup()
{
    if test -n "${SETUP_PID:-}"
    then
        kill -TERM "$SETUP_PID" 2> /dev/null || true
        wait "$SETUP_PID" 2> /dev/null || true
    fi
    for n in `jobs -p`
    do
        kill -SIGCONT $n # in case one provider was suspended
        kill $n 2> /dev/null || true
    done
    rm -rf $CONF $CONF.edited $CONF_4 $CONF_IBAN $WALLET_DB $R1FILE $R2FILE $B1FILE $B2FILE $TMP_DIR
    wait
}


# Launch the GNU Taler services (bank, exchange, merchant) via
# taler-unified-setup.sh and return once they are up.  Mirrors the setup()
# helper of exchange/src/testing/setup.sh; we cannot source that file as it is
# not installed.  Arguments are passed on to taler-unified-setup.sh.
function taler_setup()
{
    local FIFO_DIR
    local FIFO_OUT

    # Create a named pipe in a temp directory we own.
    FIFO_DIR=`mktemp -d fifo-XXXXXX`
    FIFO_OUT="$FIFO_DIR/out"
    mkfifo "$FIFO_OUT"
    # Open pipe as FD 3 (RW) and FD 4 (RO)
    exec 3<> "$FIFO_OUT" 4< "$FIFO_OUT"
    rm -rf "$FIFO_DIR"
    # '-W' is required for our termination logic to work.
    taler-unified-setup.sh -W "$@" >&3 2> taler-unified-setup.log &
    SETUP_PID=$!
    exec 3>&-
    # taler-unified-setup.sh prints "READY:$TESTROOT" once everything is up.
    # Read until EOF instead of 'sed -u /READY:/ q' so that we can tell "the
    # system came up" apart from "taler-unified-setup.sh died first".
    READY=0
    while IFS= read -r LINE <&4
    do
        case "$LINE" in
            READY:*)
                READY=1
                break
                ;;
        esac
    done
    exec 4>&-
    if test 1 != $READY
    then
        exit_skip "Failed to launch Taler services, see taler-unified-setup.log"
    fi
}


# Create a bank account at libeufin-bank.  Prints its payto URI.
# $1 = username, $2 = password, $3 = legal name, $4 = payto URI (optional)
function make_bank_account() {
    if test -z "${4:-}"
    then
        libeufin-bank create-account \
                      -c $CONF_IBAN \
                      -u "$1" -p "$2" --name "$3" 2>> iban-setup.log
    else
        libeufin-bank create-account \
                      -c $CONF_IBAN \
                      -u "$1" -p "$2" --name "$3" --payto_uri "$4" 2>> iban-setup.log
    fi
}


# Transfer funds from the debit account to the Anastasis account and have
# the Anastasis IBAN helper pick the transfer up.
# $1 = amount ($CURRENCY:X.Y), $2 = subject.
function wire_transfer_to_anastasis() {
    curl -s -X POST \
         --user "${DEBIT_USERNAME}:${DEBIT_PASSWORD}" \
         -H "Content-Type: application/json" \
         -d "{\"payto_uri\":\"${PAYTO_CREDIT}&message=$(echo "$2" | jq -sRr @uri)&amount=$1\"}" \
         "${IBAN_BANK_URL}accounts/${DEBIT_USERNAME}/transactions" > /dev/null
    anastasis-helper-authorization-iban -c $CONF_4 -t -L INFO
}


if test "${1:-}" != "free" -a "${1:-}" != "fees"
then
    echo "Launch script with either 'free' or 'fees' argument to launch providers with/without fees."
    exit 1
fi

export CONF_1="test_anastasis_reducer_1.conf"
export CONF_2="test_anastasis_reducer_2.conf"
export CONF_3="test_anastasis_reducer_3.conf"
if test $1 = 'free'
then
    CONF4="test_anastasis_reducer_4_free.conf"
else
    CONF4="test_anastasis_reducer_4.conf"
fi

# Taler configuration file will be edited, so we create one
# from the template.
export CONF=`mktemp test_reducerXXXXXX.conf`
export CONF_4=`mktemp test_reducer_4XXXXXX.conf`
export CONF_IBAN=`mktemp test_reducer_ibanXXXXXX.conf`
cp test_reducer.conf $CONF
cp $CONF4 $CONF_4

TMP_DIR=`mktemp -d keys-tmp-XXXXXX`
# wallet-core needs an sqlite3 database; a '.json' name selects the
# no-longer-supported in-memory backend.
WALLET_DB=`mktemp test_reducer_walletXXXXXX.sqlite3`
B1FILE=`mktemp test_reducer_stateB1XXXXXX`
B2FILE=`mktemp test_reducer_stateB2XXXXXX`
R1FILE=`mktemp test_reducer_stateR1XXXXXX`
R2FILE=`mktemp test_reducer_stateR2XXXXXX`
IBAN_ACTIVE='false'
SETUP_PID=''

# Install cleanup handler (except for kill -9)
trap cleanup EXIT

# Check we can actually run
if test $1 = 'fees'
then
    echo -n "Testing for taler"
    taler-exchange-httpd -h > /dev/null || exit_skip " taler-exchange required"
    taler-merchant-httpd -h > /dev/null || exit_skip " taler-merchant required"
    # note: 'taler-unified-setup.sh -h' exits non-zero (its exit trap wins)
    command -v taler-unified-setup.sh > /dev/null || exit_skip " taler-unified-setup.sh required"
    echo " FOUND"

    echo -n "Testing for libeufin-bank"
    libeufin-bank --help >/dev/null </dev/null || exit_skip " MISSING"
    echo " FOUND"

    echo -n "Testing for taler-wallet-cli"
    taler-wallet-cli -v >/dev/null </dev/null || exit_skip " MISSING"
    echo " FOUND"

    echo -n "Testing for jq"
    jq --version >/dev/null </dev/null || exit_skip " MISSING"
    echo " FOUND"
fi

echo -n "Testing for anastasis-httpd"
anastasis-httpd -h >/dev/null </dev/null || exit_skip " MISSING"
echo " FOUND"

echo -n "Initialize anastasis database ..."
# Name of the Postgres database we will use for the script.
# Will be dropped, do NOT use anything that might be used
# elsewhere
TARGET_DB_1=`anastasis-config -c $CONF_1 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"`
TARGET_DB_2=`anastasis-config -c $CONF_2 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"`
TARGET_DB_3=`anastasis-config -c $CONF_3 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"`
TARGET_DB_4=`anastasis-config -c $CONF_4 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"`

dropdb $TARGET_DB_1 >/dev/null 2>/dev/null || true
createdb $TARGET_DB_1 || exit_skip "Could not create database $TARGET_DB_1"
anastasis-dbinit -c $CONF_1 2> anastasis-dbinit_1.log
dropdb $TARGET_DB_2 >/dev/null 2>/dev/null || true
createdb $TARGET_DB_2 || exit_skip "Could not create database $TARGET_DB_2"
anastasis-dbinit -c $CONF_2 2> anastasis-dbinit_2.log
dropdb $TARGET_DB_3 >/dev/null 2>/dev/null || true
createdb $TARGET_DB_3 || exit_skip "Could not create database $TARGET_DB_3"
anastasis-dbinit -c $CONF_3 2> anastasis-dbinit_3.log
dropdb $TARGET_DB_4 >/dev/null 2>/dev/null || true
createdb $TARGET_DB_4 || exit_skip "Could not create database $TARGET_DB_4"
anastasis-dbinit -c $CONF_4 2> anastasis-dbinit_4.log

echo " OK"

if test $1 = 'fees'
then

    echo -n "Setting up Taler databases ..."
    TALER_DB=talercheck
    # bank, exchange and merchant share one database here
    taler-exchange-config -c $CONF -s exchangedb-postgres -o CONFIG -V postgres:///$TALER_DB
    taler-exchange-config -c $CONF -s merchantdb-postgres -o CONFIG -V postgres:///$TALER_DB
    taler-exchange-config -c $CONF -s libeufin-bankdb-postgres -o CONFIG -V postgres:///$TALER_DB
    taler-exchange-config -c $CONF -s exchange -o KEYDIR -V "${TMP_DIR}/keydir/"
    taler-exchange-config -c $CONF -s exchange -o REVOCATION_DIR -V "${TMP_DIR}/revdir/"
    DATA_DIR=`taler-exchange-config -f -c $CONF -s PATHS -o TALER_HOME`
    rm -rf $DATA_DIR
    dropdb $TALER_DB >/dev/null 2>/dev/null || true
    createdb $TALER_DB || exit_skip "Could not create database $TALER_DB"
    echo " OK"

    # taler-unified-setup.sh launches the bank (libeufin-bank), the exchange
    # (including the secmods and the offline key ceremony) and the merchant
    # (including taler-merchant-exchangekeyupdate, without which the merchant
    # never learns the exchange's keys and refuses to create orders).  It
    # writes the configuration it actually uses to "$CONF.edited".
    echo "Launching Taler services ..."
    taler_setup -c $CONF -L INFO -b -e -m -w -r merchant-exchange-default
    echo "Taler services up"

    BANK_PORT=`taler-exchange-config -c $CONF.edited -s libeufin-bank -o PORT`
    BANK_URL="http://localhost:${BANK_PORT}/"
    EXCHANGE_URL=`taler-exchange-config -c $CONF.edited -s exchange -o BASE_URL`
    MERCHANT_PORT=`taler-merchant-config -c $CONF.edited -s merchant -o PORT`
    MERCHANT_URL="http://localhost:${MERCHANT_PORT}/"

    echo -n "Configuring merchant instance ..."
    # POST to the merchant and fail loudly if it does not like the request.
    # $1 = path, $2 = JSON body
    function merchant_post() {
        local RESP
        local CODE

        RESP=`curl -s -w '\n%{http_code}' -H "Content-Type: application/json" \
                   -X POST -d "$2" "${MERCHANT_URL}$1"`
        CODE=`echo "$RESP" | tail -n1`
        case "$CODE" in
            20*)
                ;;
            *)
                echo " FAILED ($CODE)"
                echo "$RESP" | head -n-1
                exit_fail "merchant rejected POST /$1"
                ;;
        esac
    }
    # The default instance is called "admin" and is served at the root of the
    # merchant; the historic /instances/default/ prefix is gone.  'use_stefan'
    # is required by the current merchant protocol.
    merchant_post management/instances \
        '{"auth":{"method":"external"},"id":"admin","name":"default","address":{},"jurisdiction":{},"use_stefan":true,"default_max_wire_fee":"TESTKUDOS:1","default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_us":3600000000},"default_pay_delay":{"d_us":3600000000}}'
    # 'fortythree' is one of the accounts taler-unified-setup.sh registers
    merchant_post private/accounts \
        '{"payto_uri":"payto://x-taler-bank/localhost/fortythree?receiver-name=Forty+Three"}'
    echo " OK"

    echo -n "Preparing wallet"
    rm -f $WALLET_DB
    taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB api \
                     --expect-success 'withdrawTestBalance' \
                     "$(jq -n '
    {
        amount: "TESTKUDOS:100",
        corebankApiBaseUrl: $BANK_URL,
        exchangeBaseUrl: $EXCHANGE_URL
    }' \
    --arg BANK_URL "$BANK_URL" \
    --arg EXCHANGE_URL "$EXCHANGE_URL"
  )" 2> wallet-withdraw.log >/dev/null
    echo -n "."
    # the wallet's withdrawal only completes once the exchange has seen the
    # incoming wire transfer
    taler-exchange-wirewatch -c $CONF.edited -t 2> wirewatch-once.log
    echo -n "."
    taler-wallet-cli --wallet-db=$WALLET_DB run-until-done 2> wallet-run.log >/dev/null
    echo " OK"

    echo -n "Setting up IBAN authentication ..."
    # The IBAN challenge needs a bank that speaks IBAN and whose incoming
    # transfers Anastasis can see through the taler-revenue API.  That cannot
    # be the bank the exchange uses: libeufin-bank serves a single WIRE_TYPE,
    # and the exchange side of this test is x-taler-bank.  So we run a second,
    # IBAN-typed libeufin-bank for this one authorization method -- it also
    # matches the fact that the IBAN challenge is priced in EUR.
    IBAN_DB=anastasisibancheck
    IBAN_BANK_PORT=8092
    IBAN_BANK_URL="http://localhost:${IBAN_BANK_PORT}/"
    REASON=""
    # both IBANs must pass the checksum: libeufin-bank rejects malformed ones
    IBAN_CREDIT="DE44500105175407324931"
    IBAN_DEBIT="FR1420041010050500013M02606"
    PERSON_CREDIT_NAME="Person Credit"
    CREDIT_USERNAME=anastasis-credit-user
    CREDIT_PASSWORD=anastasis-credit-password
    DEBIT_USERNAME=anastasis-debit-user
    DEBIT_PASSWORD=anastasis-debit-password
    PAYTO_CREDIT="payto://iban/${IBAN_CREDIT}?receiver-name=Person+Credit"
    PAYTO_DEBIT="payto://iban/${IBAN_DEBIT}?receiver-name=Person+Debit"
    IBAN_REVENUE_URL="${IBAN_BANK_URL}accounts/${CREDIT_USERNAME}/taler-revenue/"
    # the challenge is priced in this currency, e.g. "EUR:1"
    IBAN_CURRENCY=`anastasis-config -c $CONF_4 -s authorization-iban -o COST | cut -d: -f1`
    cat > $CONF_IBAN <<EOF
# Generated by test_prepare.sh: the "customer's bank" for IBAN challenges.
[PATHS]
TALER_HOME = \${PWD}/test_reducer_home/
TALER_DATA_HOME = \$TALER_HOME/.local/share/taler/
TALER_CONFIG_HOME = \$TALER_HOME/.config/taler/
TALER_CACHE_HOME = \$TALER_HOME/.cache/taler/
TALER_RUNTIME_DIR = \${TMPDIR:-\${TMP:-/tmp}}/taler-system-runtime-\${USER}/

[libeufin-bank]
CURRENCY = ${IBAN_CURRENCY}
BASE_URL = ${IBAN_BANK_URL}
SERVE = tcp
PORT = ${IBAN_BANK_PORT}
WIRE_TYPE = iban
IBAN_PAYTO_BIC = SANDBOXX
DEFAULT_CUSTOMER_DEBT_LIMIT = ${IBAN_CURRENCY}:200
DEFAULT_ADMIN_DEBT_LIMIT = ${IBAN_CURRENCY}:2000
ALLOW_REGISTRATION = YES
PWD_HASH_CONFIG = { "cost": 4 }
PWD_AUTH_COMPAT = yes

[libeufin-bankdb-postgres]
CONFIG = postgres:///${IBAN_DB}
EOF
    IBAN_ACTIVE='true'
    dropdb $IBAN_DB >/dev/null 2>/dev/null || true
    if ! createdb $IBAN_DB 2>> iban-setup.log
    then
        IBAN_ACTIVE='false'
    else
        libeufin-bank dbinit -c $CONF_IBAN >> iban-setup.log 2>&1
        libeufin-bank serve -c $CONF_IBAN >> iban-setup.log 2>&1 &
        IBAN_BANK_PID=$!
        for n in `seq 1 50`
        do
            sleep 0.2
            wget --tries=1 --timeout=1 "${IBAN_BANK_URL}config" \
                 -o /dev/null -O /dev/null >/dev/null && break
        done
        make_bank_account "$CREDIT_USERNAME" "$CREDIT_PASSWORD" \
                          "$PERSON_CREDIT_NAME" "$PAYTO_CREDIT" > /dev/null &&
        make_bank_account "$DEBIT_USERNAME" "$DEBIT_PASSWORD" \
                          "Person Debit" "$PAYTO_DEBIT" > /dev/null &&
        libeufin-bank edit-account -c $CONF_IBAN \
                      --debit_threshold="${IBAN_CURRENCY}:1000" \
                      "$DEBIT_USERNAME" >> iban-setup.log 2>&1 ||
            IBAN_ACTIVE='false'
        # Make sure the bank speaks the revenue API that
        # anastasis-helper-authorization-iban expects (an empty history is
        # answered with 204), so that a version mismatch disables IBAN
        # authentication instead of failing challenges at run time.
        CODE=`curl -s -o /dev/null -w "%{http_code}" \
              --user "${CREDIT_USERNAME}:${CREDIT_PASSWORD}" \
              "${IBAN_REVENUE_URL}history?limit=1"`
        if test $IBAN_ACTIVE = 'true' &&
           test 200 != "$CODE" && test 204 != "$CODE"
        then
            REASON="revenue API at ${IBAN_REVENUE_URL} answered $CODE"
            IBAN_ACTIVE='false'
            kill $IBAN_BANK_PID 2> /dev/null || true
        fi
    fi
    if test $IBAN_ACTIVE = 'false'
    then
        echo " DISABLED (${REASON:-see iban-setup.log})"
        anastasis-config -c $CONF_4 -s authorization-iban -o ENABLED -V NO
    else
        anastasis-config -c $CONF_4 -s authorization-iban -o CURRENCY -V "${IBAN_CURRENCY}"
        anastasis-config -c $CONF_4 -s authorization-iban -o CREDIT_IBAN -V "${IBAN_CREDIT}"
        anastasis-config -c $CONF_4 -s authorization-iban -o BUSINESS_NAME -V "${PERSON_CREDIT_NAME}"
        anastasis-config -c $CONF_4 -s authorization-iban -o WIRE_GATEWAY_URL \
                         -V "${IBAN_REVENUE_URL}"
        anastasis-config -c $CONF_4 -s authorization-iban -o WIRE_GATEWAY_AUTH_METHOD -V "basic"
        anastasis-config -c $CONF_4 -s authorization-iban -o USERNAME -V "${CREDIT_USERNAME}"
        anastasis-config -c $CONF_4 -s authorization-iban -o PASSWORD -V "${CREDIT_PASSWORD}"
        anastasis-config -c $CONF_4 -s authorization-iban -o ENABLED -V YES
        echo " OK"
    fi
else
    anastasis-config -c $CONF_4 -s authorization-iban -o ENABLED -V NO
fi


echo -n "Launching anastasis services ..."
# PREFIX="valgrind --log-file=anastasis-httpd.%p.log"
PREFIX=""
$PREFIX anastasis-httpd -L INFO -c $CONF_1 2> anastasis-httpd_1.log &
PPID_1=$!
$PREFIX anastasis-httpd -L INFO -c $CONF_2 2> anastasis-httpd_2.log &
PPID_2=$!
$PREFIX anastasis-httpd -L INFO -c $CONF_3 2> anastasis-httpd_3.log &
PPID_3=$!
$PREFIX anastasis-httpd -L INFO -c $CONF_4 2> anastasis-httpd_4.log &
PPID_4=$!
export PPID_1
export PPID_2
export PPID_3
export PPID_4
echo " OK"

echo -n "Waiting for anastasis services ..."

# Wait for anastasis services to be available
for n in `seq 1 50`
do
    echo -n "."
    sleep 0.1
    OK=0
   # anastasis_01
    wget --tries=1 --timeout=1 http://localhost:8086/ -o /dev/null -O /dev/null >/dev/null || continue
    # anastasis_02
    wget --tries=1 --timeout=1 http://localhost:8087/ -o /dev/null -O /dev/null >/dev/null || continue
    # anastasis_03
    wget --tries=1 --timeout=1 http://localhost:8088/ -o /dev/null -O /dev/null >/dev/null || continue
    # anastasis_04
    wget --tries=1 --timeout=1 http://localhost:8089/ -o /dev/null -O /dev/null >/dev/null || continue
    OK=1
    break
done

if [ 1 != $OK ]
then
    exit_skip "Failed to launch anastasis services"
fi
echo "OK"

export -f wire_transfer_to_anastasis

echo "You can now run anastasis-gtk in TESTLAND."
echo " "
echo "We will now start a sub-shell. Please note:"
echo '- to terminate the test environment by leaving the sub-shell use: exit'
if test $IBAN_ACTIVE = 'true'
then
    echo '- for IBAN authentication use: wire_transfer_to_anastasis "$AMOUNT" "$SUBJECT"'
    echo "- for your customer IBAN, use: ${IBAN_DEBIT}"
    export IBAN_BANK_URL
    export PAYTO_CREDIT
    export DEBIT_USERNAME
    export DEBIT_PASSWORD
    export CONF_4
fi
if test $1 = 'fees'
then
    echo '- to pay, use: taler-wallet-cli --wallet-db=$WALLET_DB handle-uri $PAY_URI -y'
    export WALLET_DB
fi

echo '- use kill -SIGSTOP $PPID_$NUM ($NUM: 1-4) to suspend providers'

bash

exit 0
