#!/bin/bash
# Extreme amount tests for GOA: largest practical price vs atomic/tiny coin.
#
# Live denoms (exchange.hacktivism.ch): GOA:0.000001 … GOA:10
# Currency TINY / round unit: GOA:0.00000001 (Atomic-GOA)
#
# Run on the local machine (wallet) with ssh BatchMode to koopa for bank/merchant ops.
# Requires: monorepo taler-wallet-cli, passwords on koopa /root/*-password.txt
#
# Usage:
#   ./run-extreme-amount-tests.sh
#   HIGH_AMOUNT=GOA:500 TINY_AMOUNT=GOA:0.000001 ./run-extreme-amount-tests.sh
set -euo pipefail

ROOT=$(cd "$(dirname "$0")/../.." && pwd)
SCRATCH="${ROOT}/.tmp/extreme-$(date -u +%Y%m%d-%H%M%S)"
mkdir -p "$SCRATCH"
chmod 700 "$SCRATCH"

MONO="${MONO:-/Users/newkamek/src/taler/taler-typescript-core/packages/taler-wallet-cli/bin/taler-wallet-cli.mjs}"
WDB="${WDB:-$SCRATCH/wallet.sqlite3}"
EX="${EXCHANGE_URL:-https://exchange.hacktivism.ch/}"
KOOPA="${KOOPA_HOST:-koopa}"
INST="${MERCHANT_INSTANCE:-goa-demo-cp4zqk}"
BANK_PUBLIC="${BANK_PUBLIC:-https://bank.hacktivism.ch}"
MER_PUBLIC="${MER_PUBLIC:-https://taler.hacktivism.ch}"

# High price: many max-denom (GOA:10) coins. Tiny: smallest issued coin.
HIGH_AMOUNT="${HIGH_AMOUNT:-GOA:1000}"
TINY_AMOUNT="${TINY_AMOUNT:-GOA:0.000001}"
# True atomic (round unit) — may fail if no coin below 1e-6
ATOMIC_AMOUNT="${ATOMIC_AMOUNT:-GOA:0.00000001}"
# Fat bank balance for withdraw buffer
FAT_CREDIT="${FAT_CREDIT:-GOA:50000}"
FAT_USER="${FAT_USER:-extreme-fat}"
# withdraw a bit more than HIGH
WITHDRAW_AMOUNT="${WITHDRAW_AMOUNT:-GOA:1500}"

wcli() { node "$MONO" --wallet-db="$WDB" --no-throttle --skip-defaults "$@"; }

log() { printf '%s %s\n' "$(date -u +%H:%M:%S)" "$*"; }
die() { echo "FAIL: $*" >&2; exit 1; }

command -v node >/dev/null || die "node missing"
[ -f "$MONO" ] || die "wallet missing: $MONO"
ssh -o BatchMode=yes -o ConnectTimeout=10 "$KOOPA" 'echo ok' >/dev/null || die "ssh $KOOPA failed"

log "scratch=$SCRATCH"
log "HIGH=$HIGH_AMOUNT TINY=$TINY_AMOUNT ATOMIC=$ATOMIC_AMOUNT WITHDRAW=$WITHDRAW_AMOUNT FAT=$FAT_USER+$FAT_CREDIT"

# --- 1) koopa: bank account + credit + templates + orders ---
log "=== koopa setup (admin credit, templates, orders) ==="
# Generate bank password locally (not committed)
FAT_PW=$(python3 -c 'import secrets; print(secrets.token_urlsafe(12))')
echo "$FAT_PW" >"$SCRATCH/fat-bank.password"
chmod 600 "$SCRATCH/fat-bank.password"

cat >"$SCRATCH/koopa-setup.sh" <<EOS
#!/bin/bash
set +e
exec > /tmp/extreme-setup.out 2>&1
set -x
INST=$INST
FAT_USER=$FAT_USER
FAT_PW='$(printf %s "$FAT_PW" | sed "s/'/'\\\\''/g")'
FAT_CREDIT=$FAT_CREDIT
HIGH_AMOUNT=$HIGH_AMOUNT
TINY_AMOUNT=$TINY_AMOUNT
ATOMIC_AMOUNT=$ATOMIC_AMOUNT
BANK=http://127.0.0.1:9012
MER=https://127.0.0.1:9010

ADMIN_PASS=\$(tr -d '\\n' </root/bank-admin-password.txt)
MPW=\$(tr -d '\\n' </root/merchant-\${INST}-password.txt)
AUTH="Authorization: Bearer secret-token:\${MPW}"

# admin token
AT=\$(curl -sS -u "admin:\${ADMIN_PASS}" -H 'Content-Type: application/json' \\
  -d '{"scope":"readwrite"}' "\${BANK}/accounts/admin/token" \\
  | python3 -c 'import sys,json;print(json.load(sys.stdin).get("access_token",""))')
[ -n "\$AT" ] || { echo FAIL_admin_token; exit 1; }

# create or reuse bank account
code=\$(curl -sS -o /tmp/acc-create.json -w '%{http_code}' -X POST \\
  -H "Authorization: Bearer \${AT}" -H 'Content-Type: application/json' \\
  -d "{\\"username\\":\\"\${FAT_USER}\\",\\"password\\":\\"\${FAT_PW}\\",\\"name\\":\\"Extreme Fat Wallet\\",\\"is_public\\":false,\\"is_taler_exchange\\":false,\\"debit_threshold\\":\\"GOA:1000000\\"}" \\
  "\${BANK}/accounts")
echo create_account_http=\$code
cat /tmp/acc-create.json; echo
# if exists (409), reset password via admin if possible
if [ "\$code" != "200" ] && [ "\$code" != "201" ] && [ "\$code" != "204" ]; then
  echo "account may exist; try credit anyway"
fi

# credit from admin (idempotent enough — we just need lots of GOA)
python3 - <<'PY'
import json, os, urllib.request, base64
bank=os.environ.get("BANK","http://127.0.0.1:9012")
PY
# use host credit-account if present
if [ -x /root/koopa-admin-log/scripts/taler-bank/credit-account.sh ]; then
  /root/koopa-admin-log/scripts/taler-bank/credit-account.sh "\$FAT_USER" "\$FAT_CREDIT" || true
elif [ -x /Users/newkamek/src/koopa/koopa-admin-log/scripts/taler-bank/credit-account.sh ]; then
  true
else
  # inline credit
  PAYTO="payto://x-taler-bank/bank.hacktivism.ch/\${FAT_USER}?receiver-name=\${FAT_USER}&message=extreme-credit"
  UID=\$(python3 -c 'import os; a="0123456789ABCDEFGHJKMNPQRSTVWXYZ"; b=os.urandom(32); bits=0;v=0;o=[]
for byte in b:
 v=(v<<8)|byte; bits+=8
 while bits>=5:
  bits-=5; o.append(a[(v>>bits)&31])
if bits: o.append(a[(v<<(5-bits))&31])
print("".join(o))')
  curl -sS -H "Authorization: Bearer \${AT}" -H 'Content-Type: application/json' \\
    -d "{\\"payto_uri\\":\\"\${PAYTO}\\",\\"amount\\":\\"\${FAT_CREDIT}\\",\\"request_uid\\":\\"\${UID}\\"}" \\
    "\${BANK}/accounts/admin/transactions" | tee /tmp/credit.out
  echo
fi

# balance
curl -sS -H "Authorization: Bearer \${AT}" "\${BANK}/accounts/\${FAT_USER}" | tee /tmp/fat-bal.json
echo
python3 -c 'import json;d=json.load(open("/tmp/fat-bal.json"));print("FAT_BALANCE", (d.get("balance") or {}).get("amount"), (d.get("balance") or {}).get("credit_debit_indicator"))'

# store bank pw for host-side withdraw
printf '%s' "\$FAT_PW" > /root/bank-\${FAT_USER}-password.txt
chmod 600 /root/bank-\${FAT_USER}-password.txt

# templates
for id_summary_amount in \\
  "extreme-high|extreme high price|\${HIGH_AMOUNT}" \\
  "extreme-tiny|extreme tiny coin|\${TINY_AMOUNT}" \\
  "extreme-atomic|true atomic round unit|\${ATOMIC_AMOUNT}"
do
  IFS='|' read -r TID SUM AMT <<<"\$id_summary_amount"
  body=\$(python3 -c "import json; print(json.dumps({
    'template_id': '\$TID',
    'template_description': '\$SUM',
    'editable_defaults': False,
    'template_contract': {
      'summary': '\$SUM',
      'amount': '\$AMT',
      'minimum_age': 0,
    },
  }))")
  # try POST then PATCH
  code=\$(curl -sk -o /tmp/tpl.json -w '%{http_code}' -X POST -H "\$AUTH" -H 'Content-Type: application/json' \\
    -d "\$body" "\${MER}/instances/\${INST}/private/templates")
  echo "template_\$TID post=\$code \$(head -c 120 /tmp/tpl.json)"
  if [ "\$code" = "409" ] || [ "\$code" = "400" ]; then
    curl -sk -X PATCH -H "\$AUTH" -H 'Content-Type: application/json' \\
      -d "\$body" "\${MER}/instances/\${INST}/private/templates/\${TID}" -w " patch=%{http_code}\\n"
  fi
done

# also list templates
curl -sk -H "\$AUTH" "\${MER}/instances/\${INST}/private/templates" | python3 -m json.tool | head -80

# create two firm orders (high + tiny) for deterministic pay URIs
create_order() {
  local sum="\$1" amt="\$2" tag="\$3"
  curl -sk -X POST -H "\$AUTH" -H 'Content-Type: application/json' \\
    -d "{\\"order\\":{\\"summary\\":\\"\${sum}\\",\\"amount\\":\\"\${amt}\\",\\"fulfillment_message\\":\\"extreme-ok\\"},\\"create_token\\":true}" \\
    "\${MER}/instances/\${INST}/private/orders" > "/tmp/ord-\${tag}.json"
  python3 - "\$tag" <<'PY'
import json,sys
tag=sys.argv[1]
d=json.load(open(f"/tmp/ord-{tag}.json"))
print(f"ORDER_{tag.upper()}", d.get("order_id"), "token", (d.get("token") or "")[:12])
open(f"/tmp/oid-{tag}.txt","w").write(d.get("order_id") or "")
open(f"/tmp/otok-{tag}.txt","w").write(d.get("token") or "")
PY
}
create_order "extreme-high-order" "\$HIGH_AMOUNT" high
create_order "extreme-tiny-order" "\$TINY_AMOUNT" tiny
create_order "extreme-atomic-order" "\$ATOMIC_AMOUNT" atomic

# create bank integrated withdrawal for fat user
BT=\$(curl -sS -u "\${FAT_USER}:\${FAT_PW}" -H 'Content-Type: application/json' \\
  -d '{"scope":"readwrite"}' "\${BANK}/accounts/\${FAT_USER}/token" \\
  | python3 -c 'import sys,json;print(json.load(sys.stdin).get("access_token",""))')
curl -sS -H "Authorization: Bearer \${BT}" -H 'Content-Type: application/json' \\
  -d "{\\"amount\\":\\"\$WITHDRAW_AMOUNT\\",\\"exchange_url\\":\\"https://exchange.hacktivism.ch/\\"}" \\
  "\${BANK}/accounts/\${FAT_USER}/withdrawals" | tee /tmp/wd-create.json
echo
python3 - <<'PY'
import json
d=json.load(open("/tmp/wd-create.json"))
wid=d.get("withdrawal_id") or d.get("id")
uri=d.get("taler_withdraw_uri")
print("WID", wid)
print("WURI", uri)
open("/tmp/wid.txt","w").write(wid or "")
open("/tmp/wuri.txt","w").write(uri or "")
PY

# publish artifacts for hernani-readable pull
cp /tmp/oid-*.txt /tmp/otok-*.txt /tmp/wuri.txt /tmp/wid.txt /tmp/fat-bal.json /tmp/extreme-setup.out /home/hernani/ 2>/dev/null || true
chmod 644 /home/hernani/oid-*.txt /home/hernani/otok-*.txt /home/hernani/wuri.txt /home/hernani/wid.txt 2>/dev/null || true

echo SETUP_EXTREME_OK
EOS

# inject WITHDRAW_AMOUNT into script (heredoc already expanded partially - fix)
# The setup script uses $WITHDRAW_AMOUNT as shell var on koopa - need to export into script
sed -i.bak "s|\$WITHDRAW_AMOUNT|$WITHDRAW_AMOUNT|g" "$SCRATCH/koopa-setup.sh" 2>/dev/null || \
  sed -i '' "s|\$WITHDRAW_AMOUNT|$WITHDRAW_AMOUNT|g" "$SCRATCH/koopa-setup.sh"

scp -o BatchMode=yes "$SCRATCH/koopa-setup.sh" "${KOOPA}:/tmp/extreme-setup.sh"
# also scp credit-account for reliable credit
scp -o BatchMode=yes "$ROOT/scripts/taler-bank/credit-account.sh" "${KOOPA}:/tmp/credit-account.sh" || true

# run via screen window 0 (root@koopa) — screen-run cannot source local-machine paths
SESSION=$(screen -ls 2>/dev/null | awk 'match($0, /[0-9]+\.[A-Za-z0-9._-]+/) { print substr($0, RSTART, RLENGTH); exit }')
[ -n "$SESSION" ] || die "no screen session"
screen -S "$SESSION" -p 0 -X stuff $'bash /tmp/extreme-setup.sh; chmod 644 /tmp/extreme-setup.out /home/hernani/*.txt 2>/dev/null\n'

for i in $(seq 1 90); do
  if ssh -o BatchMode=yes "$KOOPA" 'grep -q SETUP_EXTREME_OK /tmp/extreme-setup.out 2>/dev/null'; then
    log "setup ready @${i}s"
    break
  fi
  sleep 1
  [ "$i" -eq 90 ] && { ssh -o BatchMode=yes "$KOOPA" 'tail -80 /tmp/extreme-setup.out' || true; die "setup timeout"; }
done
ssh -o BatchMode=yes "$KOOPA" 'cat /tmp/extreme-setup.out' >"$SCRATCH/koopa-setup.out" || true
# pull artifacts (hernani-readable)
scp -o BatchMode=yes "${KOOPA}:/home/hernani/wuri.txt" "${KOOPA}:/home/hernani/wid.txt" \
  "${KOOPA}:/home/hernani/oid-high.txt" "${KOOPA}:/home/hernani/oid-tiny.txt" \
  "${KOOPA}:/home/hernani/oid-atomic.txt" \
  "${KOOPA}:/home/hernani/otok-high.txt" "${KOOPA}:/home/hernani/otok-tiny.txt" \
  "${KOOPA}:/home/hernani/otok-atomic.txt" \
  "$SCRATCH/" 2>/dev/null || true

WURI=$(tr -d '\n' <"$SCRATCH/wuri.txt" 2>/dev/null || true)
WID=$(tr -d '\n' <"$SCRATCH/wid.txt" 2>/dev/null || true)
OID_HIGH=$(tr -d '\n' <"$SCRATCH/oid-high.txt" 2>/dev/null || true)
OID_TINY=$(tr -d '\n' <"$SCRATCH/oid-tiny.txt" 2>/dev/null || true)
OID_ATOMIC=$(tr -d '\n' <"$SCRATCH/oid-atomic.txt" 2>/dev/null || true)
TOK_HIGH=$(tr -d '\n' <"$SCRATCH/otok-high.txt" 2>/dev/null || true)
TOK_TINY=$(tr -d '\n' <"$SCRATCH/otok-tiny.txt" 2>/dev/null || true)
TOK_ATOMIC=$(tr -d '\n' <"$SCRATCH/otok-atomic.txt" 2>/dev/null || true)

log "WURI=$WURI"
log "orders high=$OID_HIGH tiny=$OID_TINY atomic=$OID_ATOMIC"
[ -n "$WURI" ] || die "no withdraw URI"
[ -n "$OID_HIGH" ] && [ -n "$OID_TINY" ] || die "orders missing"

# ensure auto-confirm watches this withdrawal
ssh -o BatchMode=yes "$KOOPA" "echo $WID >> /var/www/bank-landing/withdraw-watch.ids 2>/dev/null; sort -u /var/www/bank-landing/withdraw-watch.ids -o /var/www/bank-landing/withdraw-watch.ids 2>/dev/null; true"
# root confirm via screen
screen -S "$SESSION" -p 0 -X stuff $'BPW=$(tr -d "\\n" </root/bank-'"$FAT_USER"'-password.txt); BT=$(curl -sS -u "'"$FAT_USER"':${BPW}" -H "Content-Type: application/json" -d "{\"scope\":\"readwrite\"}" http://127.0.0.1:9012/accounts/'"$FAT_USER"'/token | python3 -c "import sys,json;print(json.load(sys.stdin).get(\"access_token\",\"\"))"); curl -sS -X POST -H "Authorization: Bearer ${BT}" http://127.0.0.1:9012/accounts/'"$FAT_USER"'/withdrawals/'"$WID"'/confirm; echo CONF_DONE\n'

# --- 2) wallet withdraw ---
log "=== wallet: add exchange + ToS ==="
wcli exchanges add "$EX" 2>&1 | tee "$SCRATCH/ex-add.out" || true
wcli exchanges update "$EX" 2>&1 | tee "$SCRATCH/ex-upd.out" || true
wcli exchanges tos "$EX" 2>&1 | tee "$SCRATCH/ex-tos.out" || true
wcli exchanges accept-tos "$EX" 2>&1 | tee "$SCRATCH/ex-accept.out" || true

log "=== withdraw accept-uri ==="
wcli withdraw accept-uri --exchange "$EX" "$WURI" 2>&1 | tee "$SCRATCH/accept-uri.out" || true

for round in $(seq 1 20); do
  log "run-until-done round $round"
  wcli run-until-done 2>&1 | tee -a "$SCRATCH/rud.out" || true
  wcli balance 2>&1 | tee "$SCRATCH/bal-$round.out"
  if grep -qE "GOA:" "$SCRATCH/bal-$round.out"; then
    # check non-zero available
    if python3 -c 'import re,sys; t=open(sys.argv[1]).read();
m=re.search(r"available[^0-9A-Z]*GOA:([0-9.]+)", t)
print(m.group(1) if m else ""); sys.exit(0 if m and float(m.group(1))>0 else 1)' "$SCRATCH/bal-$round.out" 2>/dev/null; then
      log "coins available"
      break
    fi
  fi
  # re-confirm withdraw on bank
  screen -S "$SESSION" -p 0 -X stuff $'BPW=$(tr -d "\\n" </root/bank-'"$FAT_USER"'-password.txt); BT=$(curl -sS -u "'"$FAT_USER"':${BPW}" -H "Content-Type: application/json" -d "{\"scope\":\"readwrite\"}" http://127.0.0.1:9012/accounts/'"$FAT_USER"'/token | python3 -c "import sys,json;print(json.load(sys.stdin).get(\"access_token\",\"\"))"); curl -sS -X POST -H "Authorization: Bearer ${BT}" http://127.0.0.1:9012/accounts/'"$FAT_USER"'/withdrawals/'"$WID"'/confirm 2>/dev/null; echo\n'
  sleep 3
done

wcli balance 2>&1 | tee "$SCRATCH/balance-after-withdraw.out"
wcli transactions 2>&1 | tee "$SCRATCH/tx-after-withdraw.out" | head -100

# --- 3) pay high + tiny (+ try atomic) ---
pay_order() {
  local tag="$1" oid="$2" tok="$3"
  local uri="taler://pay/taler.hacktivism.ch/instances/${INST}/${oid}/?c=${tok}"
  echo "$uri" >"$SCRATCH/pay-${tag}.uri"
  log "=== pay $tag order=$oid ==="
  log "uri=$uri"
  wcli handle-uri "$uri" 2>&1 | tee "$SCRATCH/pay-${tag}-handle.out" || true
  # some wallets use payments prepare/confirm
  wcli run-until-done 2>&1 | tee -a "$SCRATCH/pay-${tag}-rud.out" || true
  # check merchant
  ssh -o BatchMode=yes "$KOOPA" "MPW=\$(tr -d '\\n' </root/merchant-${INST}-password.txt); curl -sk -H \"Authorization: Bearer secret-token:\${MPW}\" https://127.0.0.1:9010/instances/${INST}/private/orders/${oid}" \
    >"$SCRATCH/order-${tag}-status.json" 2>/dev/null || true
  python3 - "$SCRATCH/order-${tag}-status.json" "$tag" <<'PY' || true
import json,sys
p,tag=sys.argv[1],sys.argv[2]
try:
  d=json.load(open(p))
except Exception as e:
  print(f"STATUS_{tag} unreadable", e); raise SystemExit
print(f"STATUS_{tag}", "order_status=", d.get("order_status"), "paid=", d.get("paid"),
      "wired=", d.get("wired"), "deposit_total=", d.get("deposit_total"),
      "amount=", (d.get("contract_terms") or {}).get("amount"))
PY
}

pay_order high "$OID_HIGH" "$TOK_HIGH"
wcli balance 2>&1 | tee "$SCRATCH/balance-after-high.out"

pay_order tiny "$OID_TINY" "$TOK_TINY"
wcli balance 2>&1 | tee "$SCRATCH/balance-after-tiny.out"

if [ -n "$OID_ATOMIC" ] && [ -n "$TOK_ATOMIC" ]; then
  pay_order atomic "$OID_ATOMIC" "$TOK_ATOMIC" || true
  wcli balance 2>&1 | tee "$SCRATCH/balance-after-atomic.out" || true
fi

# template URIs for docs
{
  echo "taler://pay-template/taler.hacktivism.ch/instances/${INST}/extreme-high/"
  echo "taler://pay-template/taler.hacktivism.ch/instances/${INST}/extreme-tiny/"
  echo "taler://pay-template/taler.hacktivism.ch/instances/${INST}/extreme-atomic/"
} | tee "$SCRATCH/template-uris.txt"

log "=== summary ==="
python3 - <<PY
import json,glob,os
scratch="$SCRATCH"
print("scratch", scratch)
for tag in ("high","tiny","atomic"):
  p=f"{scratch}/order-{tag}-status.json"
  if not os.path.isfile(p):
    print(tag, "no status file"); continue
  try:
    d=json.load(open(p))
    print(tag, "paid=", d.get("paid"), "status=", d.get("order_status"),
          "amount=", (d.get("contract_terms") or {}).get("amount"),
          "deposit_total=", d.get("deposit_total"))
  except Exception as e:
    print(tag, "err", e)
print("template_uris:")
print(open(f"{scratch}/template-uris.txt").read())
PY

log "done — details in $SCRATCH"
