/*
 This file is part of GNU Taler
 (C) 2022 Taler Systems S.A.
 GNU Taler is free software; you can redistribute it and/or modify it under the
 terms of the GNU General Public License as published by the Free Software
 Foundation; either version 3, or (at your option) any later version.
 GNU 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 General Public License for more details.
 You should have received a copy of the GNU General Public License along with
 GNU Taler; see the file COPYING.  If not, see 
 */
/**
 * Client for the Taler (demo-)bank.
 */
/**
 * Imports.
 */
import {
  AmountString,
  base64FromArrayBuffer,
  buildCodecForObject,
  Codec,
  codecForAny,
  codecForString,
  encodeCrock,
  generateIban,
  getRandomBytes,
  j2s,
  Logger,
  stringToBytes,
  TalerError,
  TalerErrorCode,
} from "@gnu-taler/taler-util";
import {
  checkSuccessResponseOrThrow,
  createPlatformHttpLib,
  HttpRequestLibrary,
  readSuccessResponseJsonOrThrow,
} from "@gnu-taler/taler-util/http";
const logger = new Logger("bank-api-client.ts");
export enum CreditDebitIndicator {
  Credit = "credit",
  Debit = "debit",
}
export interface BankAccountBalanceResponse {
  balance: {
    amount: AmountString;
    credit_debit_indicator: CreditDebitIndicator;
  };
}
export interface BankServiceHandle {
  readonly bankAccessApiBaseUrl: string;
  readonly http: HttpRequestLibrary;
}
export interface BankUser {
  username: string;
  password: string;
  accountPaytoUri: string;
}
export interface WithdrawalOperationInfo {
  withdrawal_id: string;
  taler_withdraw_uri: string;
}
/**
 * FIXME: Rename, this is not part of the integration test harness anymore.
 */
export interface HarnessExchangeBankAccount {
  accountName: string;
  accountPassword: string;
  accountPaytoUri: string;
  wireGatewayApiBaseUrl: string;
}
/**
 * Helper function to generate the "Authorization" HTTP header.
 */
function makeBasicAuthHeader(username: string, password: string): string {
  const auth = `${username}:${password}`;
  const authEncoded: string = base64FromArrayBuffer(stringToBytes(auth));
  return `Basic ${authEncoded}`;
}
const codecForWithdrawalOperationInfo = (): Codec =>
  buildCodecForObject()
    .property("withdrawal_id", codecForString())
    .property("taler_withdraw_uri", codecForString())
    .build("WithdrawalOperationInfo");
/**
 * @deprecated Use BankAccessApiClient or WireGatewayApi
 */
export namespace BankApi {
  // FIXME: Move to BankAccessApi?!
  export async function registerAccount(
    bank: BankServiceHandle,
    username: string,
    password: string,
    options: {
      iban?: string;
    },
  ): Promise {
    const url = new URL("testing/register", bank.bankAccessApiBaseUrl);
    const resp = await bank.http.postJson(url.href, {
      username,
      password,
      iban: options?.iban,
    });
    let paytoUri = `payto://x-taler-bank/localhost/${username}`;
    if (resp.status !== 200 && resp.status !== 202 && resp.status !== 204) {
      logger.error(`${j2s(await resp.json())}`);
      throw TalerError.fromDetail(
        TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR,
        {
          httpStatusCode: resp.status,
        },
      );
    }
    try {
      // Pybank has no body, thus this might throw.
      const respJson = await resp.json();
      // LibEuFin demobank returns payto URI in response
      if (respJson.paytoUri) {
        paytoUri = respJson.paytoUri;
      }
    } catch (e) {
      // Do nothing
    }
    return {
      password,
      username,
      accountPaytoUri: paytoUri,
    };
  }
  // FIXME: Move to BankAccessApi?!
  export async function createRandomBankUser(
    bank: BankServiceHandle,
  ): Promise {
    const username = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase();
    const password = "pw-" + encodeCrock(getRandomBytes(10)).toLowerCase();
    // FIXME: This is just a temporary workaround, because demobank is running out of short IBANs
    const iban = generateIban("DE", 15);
    return await registerAccount(bank, username, password, {
      iban,
    });
  }
  export async function confirmWithdrawalOperation(
    bank: BankServiceHandle,
    bankUser: BankUser,
    wopi: WithdrawalOperationInfo,
  ): Promise {
    const url = new URL(
      `accounts/${bankUser.username}/withdrawals/${wopi.withdrawal_id}/confirm`,
      bank.bankAccessApiBaseUrl,
    );
    logger.info(`confirming withdrawal operation via ${url.href}`);
    const resp = await bank.http.postJson(
      url.href,
      {},
      {
        headers: {
          Authorization: makeBasicAuthHeader(
            bankUser.username,
            bankUser.password,
          ),
        },
      },
    );
    logger.info(`response status ${resp.status}`);
    const respJson = await readSuccessResponseJsonOrThrow(resp, codecForAny());
    // FIXME: We don't check the status here!
  }
  export async function abortWithdrawalOperation(
    bank: BankServiceHandle,
    bankUser: BankUser,
    wopi: WithdrawalOperationInfo,
  ): Promise {
    const url = new URL(
      `accounts/${bankUser.username}/withdrawals/${wopi.withdrawal_id}/abort`,
      bank.bankAccessApiBaseUrl,
    );
    const resp = await bank.http.postJson(
      url.href,
      {},
      {
        headers: {
          Authorization: makeBasicAuthHeader(
            bankUser.username,
            bankUser.password,
          ),
        },
      },
    );
    await readSuccessResponseJsonOrThrow(resp, codecForAny());
  }
}
/**
 * @deprecated use BankAccessApiClient
 */
export namespace BankAccessApi {
  export async function getAccountBalance(
    bank: BankServiceHandle,
    bankUser: BankUser,
  ): Promise {
    const url = new URL(
      `accounts/${bankUser.username}`,
      bank.bankAccessApiBaseUrl,
    );
    const resp = await bank.http.get(url.href, {
      headers: {
        Authorization: makeBasicAuthHeader(
          bankUser.username,
          bankUser.password,
        ),
      },
    });
    return await resp.json();
  }
  export async function createWithdrawalOperation(
    bank: BankServiceHandle,
    bankUser: BankUser,
    amount: string,
  ): Promise {
    const url = new URL(
      `accounts/${bankUser.username}/withdrawals`,
      bank.bankAccessApiBaseUrl,
    );
    const resp = await bank.http.postJson(
      url.href,
      {
        amount,
      },
      {
        headers: {
          Authorization: makeBasicAuthHeader(
            bankUser.username,
            bankUser.password,
          ),
        },
      },
    );
    return readSuccessResponseJsonOrThrow(
      resp,
      codecForWithdrawalOperationInfo(),
    );
  }
}
export interface BankAccessApiClientArgs {
  baseUrl: string;
  username: string;
  password: string;
  enableThrottling?: boolean;
  allowHttp?: boolean;
}
export interface BankAccessApiCreateTransactionRequest {
  amount: AmountString;
  paytoUri: string;
}
export class WireGatewayApiClientArgs {
  accountName: string;
  accountPassword: string;
  wireGatewayApiBaseUrl: string;
  enableThrottling?: boolean;
  allowHttp?: boolean;
}
/**
 * This API look like it belongs to harness
 * but it will be nice to have in utils to be used by others
 */
export class WireGatewayApiClient {
  httpLib;
  constructor(private args: WireGatewayApiClientArgs) {
    this.httpLib = createPlatformHttpLib({
      enableThrottling: !!args.enableThrottling,
      allowHttp: !!args.allowHttp,
    });
  }
  async adminAddIncoming(params: {
    amount: string;
    reservePub: string;
    debitAccountPayto: string;
  }): Promise {
    let url = new URL(`admin/add-incoming`, this.args.wireGatewayApiBaseUrl);
    const resp = await this.httpLib.fetch(url.href, {
      method: "POST",
      body: {
        amount: params.amount,
        reserve_pub: params.reservePub,
        debit_account: params.debitAccountPayto,
      },
      headers: {
        Authorization: makeBasicAuthHeader(
          this.args.accountName,
          this.args.accountPassword,
        ),
      },
    });
    logger.info(`add-incoming response status: ${resp.status}`);
    await checkSuccessResponseOrThrow(resp);
  }
}
/**
 * This API look like it belongs to harness
 * but it will be nice to have in utils to be used by others
 */
export class BankAccessApiClient {
  httpLib;
  constructor(private args: BankAccessApiClientArgs) {
    this.httpLib = createPlatformHttpLib({
      enableThrottling: !!args.enableThrottling,
      allowHttp: !!args.allowHttp,
    });
  }
  async getTransactions(): Promise {
    const reqUrl = new URL(
      `accounts/${this.args.username}/transactions`,
      this.args.baseUrl,
    );
    const authHeaderValue = makeBasicAuthHeader(
      this.args.username,
      this.args.password,
    );
    const resp = await this.httpLib.fetch(reqUrl.href, {
      method: "GET",
      headers: {
        Authorization: authHeaderValue,
      },
    });
    const res = await readSuccessResponseJsonOrThrow(resp, codecForAny());
    logger.info(`result: ${j2s(res)}`);
  }
  async createTransaction(
    req: BankAccessApiCreateTransactionRequest,
  ): Promise {
    const reqUrl = new URL(
      `accounts/${this.args.username}/transactions`,
      this.args.baseUrl,
    );
    const authHeaderValue = makeBasicAuthHeader(
      this.args.username,
      this.args.password,
    );
    const resp = await this.httpLib.fetch(reqUrl.href, {
      method: "POST",
      body: req,
      headers: {
        Authorization: authHeaderValue,
      },
    });
    return await readSuccessResponseJsonOrThrow(resp, codecForAny());
  }
}