#!/usr/bin/env python3

import os
import sys
import click
import json
import hashlib
import errno
from datetime import datetime
import requests

# FIXME: always use qualified name
from requests import post, get, auth, delete, patch
from urllib.parse import urljoin
from getpass import getpass

# Prepares the 'auth' option to pass to requests.
def maybe_auth(sandbox_ctx):
    if (sandbox_ctx.credentials_found):
        return dict(
            auth=auth.HTTPBasicAuth(
                sandbox_ctx.username,
                sandbox_ctx.password
            )
        )
    return dict()


# Gets the account name to use in a request.  It gives
# precedence to the account name passed along the CLI options,
# and falls back to the account name found in the environment.
# It returns None if no account was found, or that was 'admin'.
# Admin is excluded because it isn't modeled like ordinary
# customers and would therefore very likely hit != 2xx response
# statuses.
def get_account_name(accountNameCli, usernameEnv):
    maybeUsername = accountNameCli
    if not maybeUsername:
        maybeUsername = usernameEnv
    if not maybeUsername:
        print("No username was found", file=sys.stderr)
        return None
    if maybeUsername == "admin":
        print("admin username not suitable", file=sys.stderr)
        return None
    return maybeUsername

# Exit the program according to the HTTP status code that
# was received.
def check_response_status(resp, expected_status_code=200):
    if resp.status_code != expected_status_code:
        print("Unexpected response status: {}".format(resp.status_code), file=sys.stderr)
        print("Response: {}".format(resp.text), file=sys.stderr)
        sys.exit(1)

# Prints the response body.
def tell_user(resp):
    print(resp.content.decode("utf-8"))

# Normalize the two components to "x/" and "y" and pass them
# to urljoin().  This avoids drop-policies from plain urljoin().
def urljoin_nodrop(a, b):
    a = a + "/" # urljoin will drop extra trailing slashes.
    b = "/".join([x for x in b.split("/") if x != ""]) # remove leading slashes.
    return urljoin(a, b)

# FIXME: deprecate this in favor of NexusContext
def fetch_env():
    if "--help" in sys.argv:
        return []
    try:
        nexus_base_url = os.environ["LIBEUFIN_NEXUS_URL"]
        nexus_username = os.environ["LIBEUFIN_NEXUS_USERNAME"]
        nexus_password = os.environ["LIBEUFIN_NEXUS_PASSWORD"]
    except KeyError:
        print(
            "Please ensure that LIBEUFIN_NEXUS_URL,"
            " LIBEUFIN_NEXUS_USERNAME, LIBEUFIN_NEXUS_PASSWORD exist"
            " in the environment"
        )
        sys.exit(1)
    return nexus_base_url, nexus_username, nexus_password


# FIXME: deprecate this in favor of NexusContext
class NexusAccess:
    def __init__(self, nexus_base_url=None, username=None, password=None):
        self.nexus_base_url = nexus_base_url
        self.username = username
        self.password = password


@click.group(help="General utility to invoke HTTP REST services offered by Nexus.")
@click.version_option(version="0.0.1-dev.1")
def cli():
    pass


@cli.group()
@click.pass_context
def facades(ctx):
    ctx.obj = NexusAccess(*fetch_env())


@cli.group()
@click.pass_context
def connections(ctx):
    ctx.obj = NexusAccess(*fetch_env())


@cli.group()
@click.pass_context
def users(ctx):
    ctx.obj = NexusContext()


@cli.group()
@click.pass_context
def permissions(ctx):
    ctx.obj = NexusContext()

@users.command("self", help="Show information about authenticated user.")
@click.pass_obj
def users_self(obj):
    url = urljoin_nodrop(obj.nexus_base_url, f"/user")
    try:
        resp = get(url, auth=auth.HTTPBasicAuth(obj.nexus_username, obj.nexus_password))
    except Exception as e:
        print(e)
        print("Could not reach nexus at " + url)
        exit(1)

    check_response_status(resp)
    tell_user(resp)

@users.command("list", help="List users")
@click.pass_obj
def list_users(obj):
    url = urljoin_nodrop(obj.nexus_base_url, f"/users")
    try:
        resp = get(url, auth=auth.HTTPBasicAuth(obj.nexus_username, obj.nexus_password))
    except Exception as e:
        print(e)
        print("Could not reach nexus at " + url)
        exit(1)

    check_response_status(resp)
    tell_user(resp)


@users.command(help="Change user's password (as superuser)")
@click.argument("username")
@click.option(
    "--new-password",
    help="New password",
    prompt=True,
    hide_input=True,
    confirmation_prompt=True,
)
@click.pass_obj
def change_password(obj, username, new_password):
    url = urljoin_nodrop(obj.nexus_base_url, f"/users/{username}/password")
    try:
        body = dict(newPassword=new_password)
        resp = post(
            url,
            json=body,
            auth=auth.HTTPBasicAuth(obj.nexus_username, obj.nexus_password),
        )
    except Exception as e:
        print(e)
        print("Could not reach nexus at " + url)
        exit(1)

    check_response_status(resp)
    tell_user(resp)


@users.command("create", help="Create a new user without superuser privileges")
@click.argument("username")
@click.option(
    "--password",
    help="Provide password instead of prompting interactively.",
    prompt=True,
    hide_input=True,
    confirmation_prompt=True,
)
@click.pass_obj
def create_user(obj, username, password):
    url = urljoin_nodrop(obj.nexus_base_url, f"/users")
    try:
        body = dict(
            username=username,
            password=password,
        )
        resp = post(
            url,
            json=body,
            auth=auth.HTTPBasicAuth(obj.nexus_username, obj.nexus_password),
        )
    except Exception as e:
        print(e)
        print("Could not reach nexus at " + url)
        exit(1)

    print(resp.content.decode("utf-8"))
    check_response_status(resp)


@permissions.command("list", help="Show permissions")
@click.pass_obj
def list_permission(obj):
    url = urljoin_nodrop(obj.nexus_base_url, f"/permissions")
    try:
        resp = get(url, auth=auth.HTTPBasicAuth(obj.nexus_username, obj.nexus_password))
    except Exception as e:
        print(e)
        print("Could not reach nexus at " + url)
        exit(1)

    print(resp.content.decode("utf-8"))
    check_response_status(resp)


@permissions.command("grant", help="Grant permission to a subject")
@click.pass_obj
@click.argument("subject-type")
@click.argument("subject-id")
@click.argument("resource-type")
@click.argument("resource-id")
@click.argument("permission-name")
def grant_permission(
    obj, subject_type, subject_id, resource_type, resource_id, permission_name
):
    url = urljoin_nodrop(obj.nexus_base_url, f"/permissions")
    try:
        permission = dict(
            subjectType=subject_type,
            subjectId=subject_id,
            resourceType=resource_type,
            resourceId=resource_id,
            permissionName=permission_name,
        )
        body = dict(
            permission=permission,
            action="grant",
        )
        resp = post(
            url,
            json=body,
            auth=auth.HTTPBasicAuth(obj.nexus_username, obj.nexus_password),
        )
    except Exception as e:
        print(e)
        print("Could not reach nexus at " + url)
        exit(1)

    print(resp.content.decode("utf-8"))
    check_response_status(resp)


@permissions.command("revoke", help="Revoke permission from a subject")
@click.pass_obj
@click.argument("subject-type")
@click.argument("subject-id")
@click.argument("resource-type")
@click.argument("resource-id")
@click.argument("permission-name")
def revoke_permission(
    obj, subject_type, subject_id, resource_type, resource_id, permission_name
):
    url = urljoin_nodrop(obj.nexus_base_url, f"/permissions")
    try:
        permission = dict(
            subjectType=subject_type,
            subjectId=subject_id,
            resourceType=resource_type,
            resourceId=resource_id,
            permissionName=permission_name,
        )
        body = dict(
            permission=permission,
            action="revoke",
        )
        resp = post(
            url,
            json=body,
            auth=auth.HTTPBasicAuth(obj.nexus_username, obj.nexus_password),
        )
    except Exception as e:
        print(e)
        print("Could not reach nexus at " + url)
        exit(1)

    print(resp.content.decode("utf-8"))
    check_response_status(resp)


@cli.group()
@click.pass_context
def accounts(ctx):
    ctx.obj = NexusAccess(*fetch_env())


class SandboxContext:
    def __init__(self):
        self.sandbox_base_url = None
        self.demobank_name = None
        self.init_sandbox_credentials()
        if not self.credentials_found:
            print("""INFO: LIBEUFIN_SANDBOX_USERNAME or LIBEUFIN_SANDBOX_PASSWORD
not found in the environment.  Won't authenticate"""
            )

    def init_sandbox_credentials(self):
        self.username = os.environ.get("LIBEUFIN_SANDBOX_USERNAME")
        self.password = os.environ.get("LIBEUFIN_SANDBOX_PASSWORD")
        self.credentials_found = False
        if self.username and self.password:
            self.credentials_found = True 

    def require_sandbox_base_url(self):
        if self.sandbox_base_url:
            return self.sandbox_base_url
        sandbox_base_url = os.environ.get("LIBEUFIN_SANDBOX_URL")
        if not sandbox_base_url:
            raise click.UsageError(
                "sandbox URL must be given as an argument or in LIBEUFIN_SANDBOX_URL"
            )
        return sandbox_base_url

    def access_api_url(self, upath):
        base = self.require_sandbox_base_url()
        # return urljoin_nodrop(demobank_base, "/access-api" + upath)
        return urljoin_nodrop(self.demobank_base_url(), "/access-api" + upath)

    # Empty upath gets just the slash-ended circuit API URL initial segment.
    def circuit_api_url(self, upath):
        circuit_base_url = urljoin_nodrop(self.demobank_base_url(), "/circuit-api")
        return urljoin_nodrop(circuit_base_url, upath)

    def demobank_base_url(self):
        base = self.require_sandbox_base_url()
        return  urljoin_nodrop(base, f"demobanks/{self.demobank_name}")

class NexusContext:
    def __init__(self):
        self._nexus_base_url = None
        self._nexus_password = None
        self._nexus_username = None

    @property
    def nexus_base_url(self):
        if self._nexus_base_url:
            return self._nexus_base_url
        val = os.environ.get("LIBEUFIN_NEXUS_URL")
        if not val:
            raise click.UsageError(
                "nexus URL not found in LIBEUFIN_NEXUS_URL"
            )
        self._nexus_base_url = val
        return val

    @property
    def nexus_username(self):
        if self._nexus_username:
            return self._nexus_username
        val = os.environ.get("LIBEUFIN_NEXUS_USERNAME")
        if not val:
            raise click.UsageError(
                "Could not find the username in LIBEUFIN_NEXUS_USERNAME"
            )
        self._nexus_username = val
        return val

    @property
    def nexus_password(self):
        if self._nexus_password:
            return self._nexus_password
        val = os.environ.get("LIBEUFIN_NEXUS_PASSWORD")
        if not val:
            raise click.UsageError(
                "Could not find the password in LIBEUFIN_NEXUS_PASSWORD"
            )
        self._nexus_password = val
        return val


@cli.group()
@click.option("--sandbox-url", help="URL for the sandbox", required=False)
@click.pass_context
def sandbox(ctx, sandbox_url):
    ctx.obj = SandboxContext()
    ctx.obj.sandbox_base_url = sandbox_url


@connections.command(help="Get key letter (typically PDF).")
@click.argument("connection-name")
@click.argument("output_file")
@click.pass_obj
def get_key_letter(obj, connection_name, output_file):
    url = urljoin_nodrop(obj.nexus_base_url, f"/bank-connections/{connection_name}/keyletter")
    try:
        resp = get(url, auth=auth.HTTPBasicAuth(obj.username, obj.password))
    except Exception as e:
        print(e)
        print("Could not reach nexus at " + url)
        exit(1)

    check_response_status(resp)

    output = open(output_file, "wb")
    output.write(resp.content)
    output.close()

@connections.command(help="export backup")
@click.option("--passphrase", help="Passphrase for locking the backup", required=True)
@click.option("--output-file", help="Where to store the backup", required=True)
@click.argument("connection-name")
@click.pass_obj
def export_backup(obj, connection_name, passphrase, output_file):
    url = urljoin_nodrop(
        obj.nexus_base_url, "/bank-connections/{}/export-backup".format(connection_name)
    )
    try:
        resp = post(
            url,
            json=dict(passphrase=passphrase),
            auth=auth.HTTPBasicAuth(obj.username, obj.password),
        )
    except Exception as e:
        print(e)
        print("Could not reach nexus at " + url)
        exit(1)

    # Will exit upon errors.
    check_response_status(resp)

    output = open(output_file, "w+")
    output.write(resp.content.decode("utf-8"))
    output.close()

    print("Backup stored in {}".format(output_file))


@connections.command(help="delete bank connection")
@click.argument("connection-name")
@click.pass_obj
def delete_connection(obj, connection_name):

    url = urljoin_nodrop(obj.nexus_base_url, "/bank-connections/delete-connection")
    try:
        resp = post(
            url,
            json=dict(bankConnectionId=connection_name),
            auth=auth.HTTPBasicAuth(obj.username, obj.password),
        )
    except Exception as e:
        print(e)
        print("Could not reach nexus at " + url)
        exit(1)

    check_response_status(resp)


@connections.command(help="restore backup")
@click.option("--backup-file", help="Back file", required=True)
@click.option("--passphrase", help="Passphrase for locking the backup", required=True)
@click.argument("connection-name")
@click.pass_obj
def restore_backup(obj, backup_file, passphrase, connection_name):
    url = urljoin_nodrop(obj.nexus_base_url, "/bank-connections")
    try:
        backup = open(backup_file, "r")
    except Exception as e:
        print(e)
        print("Could not open the backup at {}".format(backup_file))
        exit(1)
    backup_json = json.loads(backup.read())
    backup.close()

    try:
        resp = post(
            url,
            json=dict(
                name=connection_name,
                data=backup_json,
                passphrase=passphrase,
                source="backup",
            ),
            auth=auth.HTTPBasicAuth(obj.username, obj.password),
        )
    except Exception as e:
        print(e)
        print("Could not reach nexus at " + url)
        exit(1)

    check_response_status(resp)


@connections.command(help="make new EBICS bank connection")
@click.option("--ebics-url", help="EBICS URL", required=True)
@click.option("--host-id", help="Host ID", required=True)
@click.option("--partner-id", help="Partner ID", required=True)
@click.option("--ebics-user-id", help="Ebics user ID", required=True)
@click.argument("connection-name")
@click.pass_obj
def new_ebics_connection(
    obj, connection_name, ebics_url, host_id, partner_id, ebics_user_id
):
    url = urljoin_nodrop(obj.nexus_base_url, "/bank-connections")
    body = dict(
        name=connection_name,
        source="new",
        type="ebics",
        data=dict(
            ebicsURL=ebics_url,
            hostID=host_id,
            partnerID=partner_id,
            userID=ebics_user_id,
        ),
    )
    try:
        resp = post(url, json=body, auth=auth.HTTPBasicAuth(obj.username, obj.password))
    except Exception as e:
        print(e)
        print(f"Could not reach nexus at {url}")
        exit(1)

    check_response_status(resp)


@connections.command(help="Initialize the bank connection.")
@click.argument("connection-name")
@click.pass_obj
def connect(obj, connection_name):
    url = urljoin_nodrop(obj.nexus_base_url, f"/bank-connections/{connection_name}/connect")
    try:
        resp = post(
            url, json=dict(), auth=auth.HTTPBasicAuth(obj.username, obj.password)
        )
    except Exception as e:
        print(e)
        print(f"Could not reach nexus at {url}")
        exit(1)

    check_response_status(resp)
    tell_user(resp)

@connections.command(help="Import one bank account, chosen from the downloaded ones.")
@click.option(
    "--offered-account-id", help="Name of the account to import", required=True
)
@click.option(
    "--nexus-bank-account-id",
    help="Name to give to the imported account",
    required=True,
)
@click.argument("connection-name")
@click.pass_obj
def import_bank_account(
    obj, connection_name, offered_account_id, nexus_bank_account_id
):
    url = urljoin_nodrop(
        obj.nexus_base_url,
        "/bank-connections/{}/import-account".format(connection_name),
    )
    try:
        resp = post(
            url,
            json=dict(
                offeredAccountId=offered_account_id,
                nexusBankAccountId=nexus_bank_account_id,
            ),
            auth=auth.HTTPBasicAuth(obj.username, obj.password),
        )
    except Exception as e:
        print(f"Could not reach nexus at {url}: {e}")
        exit(1)

    check_response_status(resp)


@connections.command(
    help="Update list of bank accounts available through this connection."
)
@click.argument("connection-name")
@click.pass_obj
def download_bank_accounts(obj, connection_name):
    url = urljoin_nodrop(
        obj.nexus_base_url,
        "/bank-connections/{}/fetch-accounts".format(connection_name),
    )
    try:
        resp = post(
            url, json=dict(), auth=auth.HTTPBasicAuth(obj.username, obj.password)
        )
    except Exception as e:
        print(e)
        print("Could not reach nexus at " + url)
        exit(1)

    check_response_status(resp)


@connections.command(help="List the connections.")
@click.pass_obj
def list_connections(obj):
    url = urljoin_nodrop(obj.nexus_base_url, "/bank-connections")
    try:
        resp = get(
            url, json=dict(), auth=auth.HTTPBasicAuth(obj.username, obj.password)
        )
    except Exception as e:
        print(e)
        print("Could not reach nexus at " + url)
        exit(1)

    check_response_status(resp)
    tell_user(resp)


@connections.command(help="Show the status of a bank connection.")
@click.argument("connection-name")
@click.pass_obj
def show_connection(obj, connection_name):
    url = urljoin_nodrop(obj.nexus_base_url, f"/bank-connections/{connection_name}")
    try:
        resp = get(
            url, json=dict(), auth=auth.HTTPBasicAuth(obj.username, obj.password)
        )
    except Exception as e:
        print(e)
        print("Could not reach nexus at " + url)
        exit(1)

    check_response_status(resp)
    tell_user(resp)


@connections.command(help="list bank accounts hosted at one connection")
@click.argument("connection-name")
@click.pass_obj
def list_offered_bank_accounts(obj, connection_name):
    url = urljoin_nodrop(
        obj.nexus_base_url, "/bank-connections/{}/accounts".format(connection_name)
    )
    try:
        resp = get(
            url, json=dict(), auth=auth.HTTPBasicAuth(obj.username, obj.password)
        )
    except Exception as e:
        print(e)
        print("Could not reach nexus at " + url)
        exit(1)

    check_response_status(resp)
    tell_user(resp)


@accounts.command(help="Schedules a new task")
@click.argument("account-name")
@click.option("--task-name", help="Name of the task", required=True)
@click.option("--task-cronspec", help="Cronspec string", required=True)
@click.option(
    "--task-type",
    help="'fetch' (downloads transactions histories) or 'submit' (uploads payments instructions)",
    required=True,
)
@click.option(
    "--task-param-range-type",
    help="Only needed for 'fetch'.  (FIXME: link to documentation here!)",
    required=False,
)
@click.option(
    "--task-param-level",
    help="Only needed for 'fetch'.  (FIXME: link to documentation here!)",
    required=False,
)
@click.pass_obj
def task_schedule(
    obj,
    account_name,
    task_name,
    task_cronspec,
    task_type,
    task_param_range_type,
    task_param_level,
):

    url = urljoin_nodrop(obj.nexus_base_url, "/bank-accounts/{}/schedule".format(account_name))
    body = dict(name=task_name, cronspec=task_cronspec, type=task_type)
    if task_type == "fetch" and not (task_param_range_type or task_param_level):
        print("'fetch' type requires --task-param-range-type and --task-param-level")
        return

    body.update(
        dict(params=dict(rangeType=task_param_range_type, level=task_param_level))
    )
    try:
        resp = post(url, json=body, auth=auth.HTTPBasicAuth(obj.username, obj.password))
    except Exception as e:
        print(e)
        print("Could not reach nexus at " + url)
        exit(1)

    check_response_status(resp)


@accounts.command(help="Show the status of a task")
@click.argument("account-name")
@click.option("--task-name", help="Name of the task", required=True)
@click.pass_obj
def task_status(obj, account_name, task_name):
    url = urljoin_nodrop(
        obj.nexus_base_url,
        "/bank-accounts/{}/schedule/{}".format(account_name, task_name),
    )
    try:
        resp = get(url, auth=auth.HTTPBasicAuth(obj.username, obj.password))
    except Exception as e:
        print(e)
        print("Could not reach nexus " + url)
        exit(1)

    check_response_status(resp)
    tell_user(resp)


@accounts.command(help="Delete a task")
@click.argument("account-name")
@click.option("--task-name", help="Name of the task", required=True)
@click.pass_obj
def task_delete(obj, account_name, task_name):
    url = urljoin_nodrop(
        obj.nexus_base_url,
        "/bank-accounts/{}/schedule/{}".format(account_name, task_name),
    )
    try:
        resp = delete(url, auth=auth.HTTPBasicAuth(obj.username, obj.password))
    except Exception as e:
        print(e)
        print("Could not reach nexus " + url)
        exit(1)

    check_response_status(resp)


@accounts.command("list-tasks", help="List all the configured tasks")
@click.argument("account-name")
@click.pass_obj
def tasks_show(obj, account_name):
    url = urljoin_nodrop(obj.nexus_base_url, "/bank-accounts/{}/schedule".format(account_name))
    try:
        resp = get(url, auth=auth.HTTPBasicAuth(obj.username, obj.password))
    except Exception as e:
        print(e)
        print("Could not reach nexus " + url)
        exit(1)

    check_response_status(resp)
    tell_user(resp)


@accounts.command(help="Show accounts belonging to calling user")
@click.pass_obj
def show(obj):
    url = urljoin_nodrop(obj.nexus_base_url, "/bank-accounts")
    try:
        resp = get(url, auth=auth.HTTPBasicAuth(obj.username, obj.password))
    except Exception as e:
        print(f"Could not reach nexus at {url}, error: {e}")
        exit(1)

    check_response_status(resp)
    tell_user(resp)


@accounts.command(help="Prepare payment initiation debiting the account.")
@click.option(
    "--creditor-iban", help="IBAN that will receive the payment", required=True
)
@click.option(
    "--creditor-bic", help="BIC that will receive the payment", required=False
)
@click.option(
    "--creditor-name", help="Legal name that will receive the payment", required=True
)
@click.option(
    "--payment-amount", help="Amount to be paid (<currency>:X.Y)", required=True
)
@click.option("--payment-subject", help="Subject of this payment", required=True)
@click.argument("account-name")
@click.pass_obj
def prepare_payment(
    obj,
    account_name,
    creditor_iban,
    creditor_bic,
    creditor_name,
    payment_amount,
    payment_subject,
):
    url = urljoin_nodrop(
        obj.nexus_base_url, "/bank-accounts/{}/payment-initiations".format(account_name)
    )
    body = dict(
        iban=creditor_iban,
        bic=creditor_bic,
        name=creditor_name,
        subject=payment_subject,
        amount=payment_amount,
    )

    try:
        resp = post(url, json=body, auth=auth.HTTPBasicAuth(obj.username, obj.password))
    except Exception as e:
        print(e)
        print("Could not reach nexus at " + url)
        exit(1)

    check_response_status(resp)


@accounts.command(help="Submit a payment initiation.  Submit every new payment if invoked without options.")
@click.option("--payment-uuid", help="Payment unique identifier.  Submits only this payment.")
@click.argument("account-name")
@click.pass_obj
def submit_payments(obj, account_name, payment_uuid):
    if payment_uuid:
        url = urljoin_nodrop(
            obj.nexus_base_url,
            "/bank-accounts/{}/payment-initiations/{}/submit".format(
                account_name, payment_uuid
            ),
        )
    else:
        url = urljoin_nodrop(
            obj.nexus_base_url,
            "/bank-accounts/{}/submit-all-payment-initiations".format(
                account_name
            ),
        )
    try:
        resp = post(
            url, json=dict(), auth=auth.HTTPBasicAuth(obj.username, obj.password)
        )
    except Exception as e:
        print(e)
        print("Could not reach nexus at" + url)
        exit(1)

    check_response_status(resp)


@accounts.command(help="Show status of a payment initiation")
@click.option("--payment-uuid", help="payment unique identifier", required=True)
@click.argument("account-name")
@click.pass_obj
def show_payment(obj, account_name, payment_uuid):
    url = urljoin_nodrop(
        obj.nexus_base_url,
        f"/bank-accounts/{account_name}/payment-initiations/{payment_uuid}",
    )
    try:
        resp = get(url, auth=auth.HTTPBasicAuth(obj.username, obj.password))
    except Exception as e:
        print(e)
        print("Could not reach nexus at" + url)
        exit(1)

    check_response_status(resp)
    tell_user(resp)


@accounts.command(help="List payment initiations")
@click.argument("account-name")
@click.pass_obj
def list_payments(obj, account_name):
    url = urljoin_nodrop(
        obj.nexus_base_url, f"/bank-accounts/{account_name}/payment-initiations"
    )
    try:
        resp = get(url, auth=auth.HTTPBasicAuth(obj.username, obj.password))
    except Exception as e:
        print(e)
        print("Could not reach nexus at" + url)
        exit(1)

    check_response_status(resp)
    tell_user(resp)


@accounts.command(help="Delete a payment initiation")
@click.argument("account-name")
@click.option("--payment-uuid", help="payment unique identifier", required=True)
@click.pass_obj
def delete_payment(obj, account_name, payment_uuid):
    url = urljoin_nodrop(
        obj.nexus_base_url,
        f"/bank-accounts/{account_name}/payment-initiations/{payment_uuid}",
    )
    try:
        resp = delete(url, auth=auth.HTTPBasicAuth(obj.username, obj.password))
    except Exception as e:
        print(e)
        print("Could not reach nexus at" + url)
        exit(1)

    check_response_status(resp)


@accounts.command(help="Fetch transactions from the bank")
@click.option(
    "--range-type",
    default="all",
    help="Admitted values: all, latest, previous-days, since-last",
)
@click.option("--level", default="all", help="Admitted values: report, statement, all")
@click.argument("account-name")
@click.pass_obj
def fetch_transactions(obj, account_name, range_type, level):
    url = urljoin_nodrop(
        obj.nexus_base_url, "/bank-accounts/{}/fetch-transactions".format(account_name)
    )
    try:
        resp = requests.post(
            url,
            json=dict(rangeType=range_type, level=level),
            auth=auth.HTTPBasicAuth(obj.username, obj.password),
        )
    except Exception as e:
        print(e)
        print("Could not reach nexus " + url)
        exit(1)

    check_response_status(resp)
    tell_user(resp)


@accounts.command(help="Get transactions from the simplified nexus JSON API")
@click.option(
    "--compact/--no-compact",
    help="Tells only amount/subject for each payment",
    required=False,
    default=False,
)
@click.argument("account-name")
@click.pass_obj
def transactions(obj, compact, account_name):
    url = urljoin_nodrop(
        obj.nexus_base_url, "/bank-accounts/{}/transactions".format(account_name)
    )
    try:
        resp = get(url, auth=auth.HTTPBasicAuth(obj.username, obj.password))
    except Exception as e:
        print(e)
        print("Could not reach nexus " + url)
        exit(1)

    if compact and resp.status_code == 200:
        for payment in resp.json()["transactions"]:
            for entry in payment["batches"]:
                for expected_singleton in entry["batchTransactions"]:
                    print(
                        "{}, {}".format(
                            expected_singleton["details"][
                                "unstructuredRemittanceInformation"
                            ],
                            expected_singleton["amount"],
                        )
                    )
    else:
        tell_user(resp)
    check_response_status(resp)


@facades.command("list", help="List active facades in the Nexus")
@click.pass_obj
def list_facades(obj):
    url = urljoin_nodrop(obj.nexus_base_url, "/facades")
    try:
        resp = get(url, auth=auth.HTTPBasicAuth(obj.username, obj.password))
    except Exception as e:
        print(f"Could not reach nexus (at {obj.nexus_base_url}): {e}")
        exit(1)

    check_response_status(resp)
    tell_user(resp)


@facades.command(
    "new-anastasis-facade", help="create a new Anastasis facade"
)
@click.option("--facade-name", help="Name of the facade", required=True)
@click.option("--currency", help="Facade's currency", required=True)
@click.argument("connection-name")
@click.argument("account-name")
@click.pass_obj
def new_anastasis_facade(obj, facade_name, connection_name, account_name, currency):
    url = urljoin_nodrop(obj.nexus_base_url, "/facades")
    try:
        resp = post(
            url,
            auth=auth.HTTPBasicAuth(obj.username, obj.password),
            json=dict(
                name=facade_name,
                type="anastasis",
                config=dict(
                    currency=currency,
                    bankAccount=account_name,
                    bankConnection=connection_name,
                    reserveTransferLevel="UNUSED",
                    intervalIncremental="UNUSED",
                ),
            ),
        )
    except Exception as e:
        print(f"Could not reach nexus (at {obj.nexus_base_url}): {e}")
        exit(1)

    check_response_status(resp)


@facades.command(
    "new-taler-wire-gateway-facade", help="create a new Taler Wire Gateway facade"
)
@click.option("--facade-name", help="Name of the facade", required=True)
@click.option("--currency", help="Facade's currency", required=True)
@click.argument("connection-name")
@click.argument("account-name")
@click.pass_obj
def new_twg_facade(obj, facade_name, connection_name, account_name, currency):
    url = urljoin_nodrop(obj.nexus_base_url, "/facades")
    try:
        resp = post(
            url,
            auth=auth.HTTPBasicAuth(obj.username, obj.password),
            json=dict(
                name=facade_name,
                type="taler-wire-gateway",
                config=dict(
                    currency=currency,
                    bankAccount=account_name,
                    bankConnection=connection_name,
                    reserveTransferLevel="UNUSED",
                    intervalIncremental="UNUSED",
                ),
            ),
        )
    except Exception as e:
        print(f"Could not reach nexus (at {obj.nexus_base_url}): {e}")
        exit(1)

    check_response_status(resp)


@sandbox.group("ebicshost", help="manage EBICS hosts")
@click.pass_context
def sandbox_ebicshost(ctx):
    pass


@sandbox.command("check", help="check sandbox status")
@click.pass_obj
def check_sandbox_status(obj):
    sandbox_base_url = obj.require_sandbox_base_url()
    url = urljoin_nodrop(sandbox_base_url, "/")
    try:
        resp = get(url)
    except Exception as e:
        print(e)
        print("Could not reach sandbox")
        exit(1)

    check_response_status(resp)
    tell_user(resp)


@sandbox_ebicshost.command("create", help="Create an EBICS host")
@click.option("--host-id", help="EBICS host ID", required=True, prompt=True)
@click.pass_obj
def make_ebics_host(obj, host_id):
    sandbox_base_url = obj.require_sandbox_base_url()
    url = urljoin_nodrop(sandbox_base_url, "/admin/ebics/hosts")
    try:
        resp = post(
            url,
            json=dict(hostID=host_id, ebicsVersion="2.5"),
            **maybe_auth(obj)
        )
    except Exception as e:
        print(e)
        print("Could not reach sandbox")
        exit(1)

    check_response_status(resp)


@sandbox_ebicshost.command("list", help="List EBICS hosts.")
@click.pass_obj
def list_ebics_host(obj):
    sandbox_base_url = obj.require_sandbox_base_url()
    url = urljoin_nodrop(sandbox_base_url, "/admin/ebics/hosts")
    try:
        resp = get(url, **maybe_auth(obj))
    except Exception as e:
        print(e)
        print("Could not reach sandbox")
        exit(1)

    check_response_status(resp)
    tell_user(resp)


@sandbox.group("ebicssubscriber", help="manage EBICS subscribers")
@click.pass_context
def sandbox_ebicssubscriber(ctx):
    pass


@sandbox_ebicssubscriber.command("create", help="Create an EBICS subscriber.")
@click.option("--host-id", help="Ebics host ID", required=True, prompt=True)
@click.option("--partner-id", help="Ebics partner ID", required=True, prompt=True)
@click.option("--user-id", help="Ebics user ID", required=True, prompt=True)
@click.pass_obj
def create_ebics_subscriber(obj, host_id, partner_id, user_id):
    sandbox_base_url = obj.require_sandbox_base_url()
    url = urljoin_nodrop(sandbox_base_url, "/admin/ebics/subscribers")
    try:
        resp = post(
            url,
            json=dict(hostID=host_id, partnerID=partner_id, userID=user_id),
            **maybe_auth(obj)
        )
    except Exception as e:
        print(e)
        print("Could not reach sandbox")
        exit(1)

    check_response_status(resp)


@sandbox_ebicssubscriber.command("list", help="List EBICS subscribers.")
@click.pass_obj
def list_ebics_subscriber(obj):
    sandbox_base_url = obj.require_sandbox_base_url()
    url = urljoin_nodrop(sandbox_base_url, "/admin/ebics/subscribers")
    try:
        resp = get(url, **maybe_auth(obj))
    except Exception as e:
        print(e)
        print("Could not reach sandbox")
        exit(1)

    check_response_status(resp)
    tell_user(resp)


@sandbox.group("ebicsbankaccount", help="manage EBICS bank accounts")
@click.pass_context
def sandbox_ebicsbankaccount(ctx):
    pass


@sandbox_ebicsbankaccount.command(
    "create",
    help="Create a bank account for an existing EBICS subscriber.  This operation is deprecated because it doesn't associate any user profile to the bank account being created."
)
@click.option("--iban", help="IBAN", required=True)
@click.option("--bic", help="BIC", required=True)
@click.option("--person-name", help="bank account owner name", required=True)
@click.option("--account-name", help="label of this bank account", required=True)
@click.option("--ebics-user-id", help="user ID of the Ebics subscriber", required=True)
@click.option("--ebics-host-id", help="host ID of the Ebics subscriber", required=True)
@click.option(
    "--ebics-partner-id", help="partner ID of the Ebics subscriber", required=True
)
@click.pass_obj
def associate_bank_account(
    obj,
    iban,
    bic,
    person_name,
    account_name,
    ebics_user_id,
    ebics_host_id,
    ebics_partner_id,
):
    sandbox_base_url = obj.require_sandbox_base_url()
    url = urljoin_nodrop(sandbox_base_url, "/admin/ebics/bank-accounts")
    body = dict(
        subscriber=dict(
            userID=ebics_user_id, partnerID=ebics_partner_id, hostID=ebics_host_id
        ),
        iban=iban,
        bic=bic,
        name=person_name,
        label=account_name,
    )

    try:
        resp = post(
            url, json=body,
            **maybe_auth(obj)
        )
    except Exception as e:
        print(e)
        print("Could not reach sandbox")
        exit(1)

    check_response_status(resp)


@sandbox.group("bankaccount", help="manage bank accounts")
@click.pass_context
def sandbox_bankaccount(ctx):
    pass


# This group deals with the new Access API
# and the 'demobank' model.
@sandbox.group(
    "demobank",
    help="Subcommands for the 'demobank' model and the Access API."
)
@click.option(
    "--demobank-name",
    help="Defaults to 'default'",
    required=False,
    default="default"
    )
@click.pass_context
def sandbox_demobank(ctx, demobank_name):
    ctx.obj.demobank_name = demobank_name
    pass

@sandbox_demobank.command("list-transactions", help="List transactions.")
@click.option(
    "--bank-account",
    help="Label of the bank account to be debited for the transaction.",
    required=True
)
@click.pass_obj
def sandbox_demobank_list_transactions(obj, bank_account):
    url = obj.access_api_url (f"/accounts/{bank_account}/transactions")
    try:
        resp = get(url, **maybe_auth(obj))
    except Exception as e:
        print(e)
        print("Could not reach sandbox at " + url)
        exit(1)

    check_response_status(resp)
    tell_user(resp)


@sandbox_demobank.command("new-transaction", help="Initiate a new transaction.")
@click.option(
    "--bank-account",
    help="Label of the bank account to be debited for the transaction.",
    required=True
)
# Including the subject in the payto to match the
# payto returned by the merchant backend helper program
# to create tip reserves.
@click.option(
    "--payto-with-subject",
    help="Payto address including the subject as a query parameter.",
    required=True
)
@click.option(
    "--amount",
    help="Amount to transfer, in the CUR:X.Y format.",
    required=True
)
@click.pass_obj
def sandbox_demobank_new_transaction(obj, bank_account, payto_with_subject, amount):
    url = obj.access_api_url (f"/accounts/{bank_account}/transactions")
    try:
        body = dict(paytoUri=payto_with_subject, amount=amount)
        resp = post(
            url,
            json=body,
            **maybe_auth(obj)
        )
    except Exception as e:
        print(e)
        print("Could not reach sandbox at " + url)
        exit(1)

    check_response_status(resp)

@sandbox_demobank.command("info", help="Return basic information of a bank account")
@click.option(
    "--bank-account",
    help="Label of the bank account whose information should be returned.",
    required=True
)
@click.pass_obj
def sandbox_demobank_info(obj, bank_account):
    url = obj.access_api_url (f"/accounts/{bank_account}")
    try:
        resp = get(url, **maybe_auth(obj))
    except Exception as e:
        print(e)
        print("Could not reach sandbox")
        exit(1)

    check_response_status(resp)
    tell_user(resp)

@sandbox_demobank.command(
  "debug-url",
  help="Prints the base URL for the demobank to address, according to the related values passed via the environment or the command line."
)
@click.pass_obj
def debug_url(obj):
    print("demobank URL", obj.demobank_base_url())
    print("access API registration URL", obj.access_api_url("/testing/register"))

@sandbox_demobank.command("delete",
    help="""delete bank account"""
)
@click.option(
    "--bank-account",
    help="Label of the bank account to delete.",
    required=True
)
@click.pass_obj
def sandbox_demobank_delete(obj, bank_account):
    url = obj.access_api_url (f"/accounts/{bank_account}")
    try:
        resp = delete(url, **maybe_auth(obj))
    except Exception as e:
        print(e)
        print("Could not reach sandbox at " + url)
        exit(1)
    check_response_status(resp)

@sandbox_demobank.command("register",
    help="""register a new bank account.  Credentials will be taken
    from the LIBEUFIN_SANDBOX_{USERNAME, PASSWORD} env variables.  Note
    that the username will be both the username to login at the bank
    and the bank account label"""
)
@click.option(
    "--public/--no-public",
    default=False,
    help="Decides whether a bank account is public.",
)
@click.option(
    "--name",
    default="",
    help="Person name",
)
@click.option(
    "--iban",
    help="Uses this IBAN, instead of a random one.",
)
@click.pass_obj
def sandbox_demobank_register(obj, public, name, iban):
    url = obj.access_api_url ("/testing/register")
    if not obj.credentials_found:
        print("WARNING: registering with unset credentials in the environment!!")
    req = dict(username=obj.username, password=obj.password, isPublic=public)
    if name != "":
        req.update(name=name)
    if iban:
        req.update(iban=iban)
    try:
        resp = post(url, json=req)
    except Exception as e:
        print(e)
        print("Could not reach sandbox at " + url)
        exit(1)
    check_response_status(resp)

@sandbox_demobank.command("new-ebicssubscriber",
    help="Associate a new Ebics subscriber to an existing bank account."
)
@click.option("--host-id", help="Ebics host ID", required=True)
@click.option("--partner-id", help="Ebics partner ID", required=True)
@click.option("--user-id", help="Ebics user ID", required=True)
@click.option(
    "--bank-account",
    help="Label of the bank account to associate with this Ebics subscriber",
    required=True
)
@click.pass_obj
def sandbox_demobank_ebicssubscriber(obj, host_id, partner_id, user_id, bank_account):
    demobank_url = obj.demobank_base_url()
    url = urljoin_nodrop(demobank_url, "/ebics/subscribers")
    try:
        resp = post(url,
            json=dict(
                hostID=host_id,
                partnerID=partner_id,
                userID=user_id,
                demobankAccountLabel=bank_account
            ),
            **maybe_auth(obj)
        )
    except Exception as e:
        print(e)
        print("Could not reach sandbox")
        exit(1)
    check_response_status(resp)

@sandbox_bankaccount.command("list", help="List accounts")
@click.pass_obj
def bankaccount_list(obj):
    sandbox_base_url = obj.require_sandbox_base_url()
    url = urljoin_nodrop(sandbox_base_url, f"/admin/bank-accounts")
    try:
        resp = get(url, **maybe_auth(obj))
    except Exception as e:
        print(e)
        print("Could not reach sandbox")
        exit(1)

    check_response_status(resp)
    tell_user(resp)


@sandbox_bankaccount.command("transactions", help="List transactions")
@click.argument("account-label")
@click.pass_obj
def transactions_list(obj, account_label):
    sandbox_base_url = obj.require_sandbox_base_url()
    url = urljoin_nodrop(
        sandbox_base_url, f"/admin/bank-accounts/{account_label}/transactions"
    )
    try:
        resp = get(url, **maybe_auth(obj))
    except Exception as e:
        print(e)
        print("Could not reach sandbox")
        exit(1)

    check_response_status(resp)
    tell_user(resp)


@sandbox_bankaccount.command("generate-transactions", help="Generate test transactions")
@click.argument("account-label")
@click.pass_obj
def bankaccount_generate_transactions(obj, account_label):
    sandbox_base_url = obj.require_sandbox_base_url()
    url = urljoin_nodrop(
        sandbox_base_url,
        f"/admin/bank-accounts/{account_label}/generate-transactions"
    )
    try:
        resp = post(url, **maybe_auth(obj))
    except Exception as e:
        print(e)
        print("Could not reach sandbox")
        exit(1)

    check_response_status(resp)


@sandbox_bankaccount.command(help="Book an incoming payment in the sandbox")
@click.argument("account-name")
@click.option("--debtor-iban", help="IBAN sending the payment", prompt=True)
@click.option("--debtor-bic", help="BIC sending the payment", prompt=True)
@click.option(
    "--debtor-name", help="name of the person who is sending the payment", prompt=True
)
@click.option("--amount", help="amount with currency (currency:x.y)", prompt=True)
@click.option("--subject", help="payment subject", prompt=True)
@click.pass_obj
def simulate_incoming_transaction(
    obj,
    account_name,
    debtor_iban,
    debtor_bic,
    debtor_name,
    amount,
    subject,
):
    sandbox_base_url = obj.require_sandbox_base_url()
    url = urljoin_nodrop(
        sandbox_base_url,
        f"/admin/bank-accounts/{account_name}/simulate-incoming-transaction",
    )
    body = dict(
        debtorIban=debtor_iban,
        debtorBic=debtor_bic,
        debtorName=debtor_name,
        amount=amount,
        subject=subject,
    )
    try:
        resp = post(
            url,
            json=body,
            **maybe_auth(obj)
        )
    except Exception as e:
        print(e)
        print("Could not reach sandbox")
        exit(1)

    check_response_status(resp)


# The commands below request to the latest CIRCUIT API.

@sandbox_demobank.command(
  "circuit-cashout-confirm",
  help="Confirm a cash-out operation.  Only the author is allowed (no admin)."
)
@click.option(
    "--tan",
    help="TAN that authorizes the cash-out operaion.",
    required=True,
    prompt=True
)
@click.option(
    "--uuid",
    help="UUID of the cash-out operation to confirm.",
    required=True,
    prompt=True
)
@click.pass_obj
def circuit_cashout_confirm(obj, tan, uuid):
    cashout_confirm_endpoint = obj.circuit_api_url(f"cashouts/{uuid}/confirm")
    req = dict(tan=tan)
    try:
        resp = post(
            cashout_confirm_endpoint,
            json=req,
            **maybe_auth(obj)
        )
    except Exception as e:
        print(e)
        print("Could not reach the bank at " + cashout_confirm_endpoint)
        exit(1)

    check_response_status(resp, 204)
    

@sandbox_demobank.command(
  "circuit-cashout-abort",
  help="Abort a cash-out operation.  Admin and author are allowed to request."
)
@click.option(
    "--uuid",
    help="UUID of the cash-out operation to abort.",
    required=True,
    prompt=True
)
@click.pass_obj
def circuit_cashout_abort(obj, uuid):
    cashout_abort_endpoint = obj.circuit_api_url(f"cashouts/{uuid}/abort")
    try:
        resp = post(
            cashout_abort_endpoint,
            **maybe_auth(obj)
        )
    except Exception as e:
        print(e)
        print("Could not reach the bank at " + cashout_abort_endpoint)
        exit(1)

    check_response_status(resp, 204)

@sandbox_demobank.command(
  "circuit-cashout-details",
  help="Retrieve status information about one cash-out operation.  Admin and author are allowed to request."
)
@click.option(
    "--uuid",
    help="UUID of the cash-out operation to retrieve.",
    required=True,
    prompt=True
)
@click.pass_obj
def circuit_cashout_info(obj, uuid):
    cashout_info_endpoint = obj.circuit_api_url(f"cashouts/{uuid}")
    try:
        resp = get(
            cashout_info_endpoint,
            **maybe_auth(obj)
        )
    except Exception as e:
        print(e)
        print("Could not reach the bank at " + cashout_info_endpoint)
        exit(1)

    check_response_status(resp)
    tell_user(resp)

@sandbox_demobank.command(
  "circuit-delete-account",
  help="Delete one account.  Only available to the administrator and for accounts with zero balance."
)
@click.option(
    "--username",
    help="account to delete",
    required=True,
    prompt=True
)
@click.pass_obj
def circuit_delete(obj, username):

    # Check that admin wasn't specified.
    # Note: even if 'admin' gets through here,
    # the bank can't delete it because its profile
    # doesn't get a ordinary entry into the database.
    if username == "admin":
        print("Won't delete 'admin'", file=sys.stderr)
        exit(1)

    # Do not check credentials to let the --no-auth case
    # function, in case the bank allows it.
    account_deletion_endpoint = obj.circuit_api_url(f"accounts/{username}")
    try:
        resp = delete(
            account_deletion_endpoint,
            **maybe_auth(obj)
        )
    except Exception as e:
        print(e)
        print("Could not reach sandbox at " + account_deletion_endpoint)
        exit(1)
  
    check_response_status(resp, expected_status_code=204)


@sandbox_demobank.command(
  "circuit-register",
  help="Register a new account with cash-out capabilities.  It needs administrator credentials, and the new account password exported in LIBEUFIN_NEW_CIRCUIT_ACCOUNT_PASSWORD."
)
@click.option(
    "--username",
    help="new account username",
    required=True,
    prompt=True
)
@click.option(
    "--cashout-address",
    help="Payto address where to send fiat payments on cash-outs",
    required=True,
    prompt=True
)
@click.option(
    "--name",
    help="Legal name associated to the account.",
    required=True,
    prompt=True
)
@click.option(
    "--phone",
    help="SMS where to send the cash-out TAN.",
)
@click.option(
    "--email",
    help="E-mail address where to send the cash-out TAN.",
)
@click.option(
    "--internal-iban",
    help="Which IBAN to associate to this account.  The IBAN participates only in the local currency circuit.  If missing, the bank generates one.",
)
@click.pass_obj
def circuit_register(
    obj,
    username,
    cashout_address,
    name,
    phone,
    email,
    internal_iban
):
    # Check admin is requesting.
    if (obj.username != "admin"):
        print("Not running as 'admin'.  Won't request", file=sys.stderr)
        exit(1)
    new_account_password = os.environ.get("LIBEUFIN_NEW_CIRCUIT_ACCOUNT_PASSWORD")
    # Check that the new account password got
    # exported into the environment.
    if not new_account_password:
        print("LIBEUFIN_NEW_CIRCUIT_ACCOUNT_PASSWORD not found in the environment", file=sys.stderr)
        exit(1)

    # Get the bank base URL.
    registration_endpoint = obj.circuit_api_url("accounts")

    contact_data = dict()
    if (phone):
        contact_data.update(phone=phone)
    if (email):
        contact_data.update(email=email)
    req = dict(
        contact_data=contact_data,
        username=username,
        password=new_account_password,
        name=name,
        cashout_address=cashout_address
    )
    if internal_iban:
        req.update(internal_iban=internal_iban)
    try:
        resp = post(
            registration_endpoint,
            json=req,
            **maybe_auth(obj)
        )
    except Exception as e:
        print(e)
        print("Could not reach sandbox at " + registration_endpoint)
        exit(1)

    check_response_status(resp, expected_status_code=204)


@sandbox_demobank.command(
  "circuit-reconfig",
  help="Reconfigure an account with cash-out capabilities.  It needs administrator or owner credentials"
)
@click.option(
    "--phone",
    help="Phone number for the SMS TAN",
)
@click.option(
    "--email",
    help="E-mail address for receiving the TAN",
)
@click.option(
    "--cashout-address",
    help="Payto address where to send fiat payments on cash-outs",
    required=True,
    prompt=True
)
@click.option(
    "--username",
    help="Username associated with the account to reconfigure.  It defaults to LIBEUFIN_SANDBOX_USERNAME and doesn't accept 'admin'.",
)
@click.pass_obj
def circuit_reconfig(
  obj,
  phone,
  email,
  cashout_address,
  username
):
    resource_name = get_account_name(username, obj.username)
    if not resource_name:
        print("Could not find any username to reconfigure.", file=sys.stderr)
    reconfig_endpoint = obj.circuit_api_url(f"accounts/{resource_name}")
    contact_data = dict()
    if (phone):
        contact_data.update(phone=phone)
    if (email):
        contact_data.update(email=email)
    req = dict(contact_data=contact_data, cashout_address=cashout_address)

    try:
        resp = patch(
            reconfig_endpoint,
            json=req,
            **maybe_auth(obj)
        )
    except Exception as e:
        print(e)
        print("Could not reach sandbox at " + reconfig_endpoint)
        exit(1)

    check_response_status(resp, expected_status_code=204)


@sandbox_demobank.command(
  "circuit-password-reconfig",
  help="Ask interactively to change the password. It needs administrator or owner credentials"
)
@click.option(
    "--username",
    help="Username whose password will change.  Defaults to LIBEUFIN_SANDBOX_USERNAME and doesn't accept 'admin' as a value.",
    required=False
)
@click.pass_obj
def password_reconfig(obj, username):
    resource_name = get_account_name(username, obj.username)
    if not resource_name:
        print(
            "Couldn't find the username whose password should change.",
            file=sys.stderr
        )
        exit(1)
    print(f"Change password for account: {resource_name}.")
    new_password = click.prompt(
        "Enter the new password",
        hide_input=True,
        type=str
    )
    confirm_new_password = click.prompt(
        "Enter the new password again",
        hide_input=True,
        type=str
    )
    if (new_password != confirm_new_password):
        print("The password entered the second time didn't match.", file=sys.stderr)
        exit(1)
    password_reconfig_endpoint = obj.circuit_api_url(f"accounts/{resource_name}/auth")
    req = dict(new_password=confirm_new_password)
    try:
        resp = patch(
            password_reconfig_endpoint,
            json=req,
            **maybe_auth(obj)
        )
    except Exception as e:
        print(e)
        print("Could not reach sandbox at " + password_reconfig_endpoint)
        exit(1)

    check_response_status(resp, expected_status_code=204)


@sandbox_demobank.command(
  "circuit-cashout",
  help="Create a cash-out operation.  If successful, the user gets a TAN."
)
@click.option(
    "--subject",
    help="Payment subject to associate to the outgoing and incoming payments that are associated with this cash-out operation.",
    required=False
)
@click.option(
    "--amount-debit",
    help="Amount that will debited to the local currency account, in the <currency>:X.Y format.",
    required=True,
    prompt=True
)
@click.option(
    "--amount-credit",
    help="Amount that will credited to the fiat currency account, in the <currency>:X.Y format.",
    required=True,
    prompt=True
)
@click.option(
    "--tan-channel",
    help="Indicates how to send the TAN to the user: 'sms', 'email' and 'file' are valid values.  If missing, the bank defaults to SMS.  'file' makes the server write the TAN to /tmp/libeufin-cashout-tan.txt, normally used for testing.",
    required=False,
)
@click.pass_obj
def circuit_cashout(obj, subject, amount_debit, amount_credit, tan_channel):
    # (not) resorting auth credentials, if they're None, request fails at the server.
    # Craft the request.  
    req = dict(
        amount_debit=amount_debit,
        amount_credit=amount_credit
    )
    if subject:
        req.update(subject=subject)
    if tan_channel:
        req.update(tan_channel=tan_channel)
    cashout_creation_endpoint = obj.circuit_api_url("cashouts")
    try:
        resp = post(
            cashout_creation_endpoint,
            json=req,
            **maybe_auth(obj)
        )
    except Exception as e:
        print(e)
        print("Could not reach sandbox at " + cashout_creation_endpoint)
        exit(1)
  
    check_response_status(resp, expected_status_code=202)
    tell_user(resp) # Communicates back the operation UUID.

@sandbox_demobank.command(
  "circuit-account-info",
  help="Retrieve Circuit information about one account.  Useful to get cash-out address and contact details."
)
@click.option(
    "--username",
    help="Username of the account to retrieve.  It defaults to LIBEUFIN_SANDBOX_USERNAME and doesn't accept 'admin'.",
)
@click.pass_obj
def circuit_account_info(obj, username):
    resource_name = get_account_name(username, obj.username)
    if not resource_name:
        print(
            "Couldn't find the username whose account is being retrieved.",
            file=sys.stderr
        )
        exit(1)
    # resource_name != admin
    account_info_endpoint = obj.circuit_api_url(f"accounts/{resource_name}")
    try:
        resp = get(
            account_info_endpoint,
            **maybe_auth(obj)
        )
    except Exception as e:
        print(e)
        print("Could not reach the bank at " + account_info_endpoint)
        exit(1)

    check_response_status(resp)
    tell_user(resp)


@sandbox_demobank.command(
  "circuit-accounts",
  help="Gets the list of all the accounts managed by the Circuit.  Only 'admin' allowed"
)
@click.pass_obj
def circuit_accounts(obj):
    # Check admin is requesting.
    if (obj.username != "admin"):
        print("Not running as 'admin'.  Won't request", file=sys.stderr)
        exit(1)
    accounts_endpoint = obj.circuit_api_url(f"accounts")
    try:
        resp = get(
            accounts_endpoint,
            **maybe_auth(obj)
        )
    except Exception as e:
        print(e)
        print("Could not reach the bank at " + accounts_endpoint)
        exit(1)

    check_response_status(resp)
    tell_user(resp)


@sandbox_demobank.command(
  "circuit-cashouts",
  help="Gets the list of all the pending and confirmed cash-out operations."
)
@click.pass_obj
def circuit_cashouts(obj):
    # Check admin is requesting.
    if (obj.username != "admin"):
        print("Not running as 'admin'.  Won't request", file=sys.stderr)
        exit(1)
    cashouts_endpoint = obj.circuit_api_url(f"cashouts")
    try:
        resp = get(
            cashouts_endpoint,
            **maybe_auth(obj)
        )
    except Exception as e:
        print(e)
        print("Could not reach the bank at " + cashouts_endpoint)
        exit(1)

    check_response_status(resp)
    tell_user(resp)

cli()
