/*
 This file is part of GNU Taler
 (C) 2022-2024 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 <http://www.gnu.org/licenses/>
 */

import {
  AbsoluteTime,
  AccessToken,
  HttpStatusCode,
  LibtoolVersion,
  LongPollParams,
  OperationFail,
  OperationOk,
  PaginationParams,
  TalerError,
  TalerErrorCode,
  TokenRequest,
  UserAndToken,
  assertUnreachable,
  carefullyParseConfig,
  codecForTalerCommonConfigResponse,
  codecForTokenInfoList,
  codecForTokenSuccessResponse,
  codecOptional,
  codecOptionalDefault,
  opKnownAlternativeHttpFailure,
  opKnownHttpFailure,
  opKnownTalerFailure,
} from "@gnu-taler/taler-util";
import {
  HttpRequestLibrary,
  createPlatformHttpLib,
  readSuccessResponseJsonOrThrow,
  readTalerErrorResponse,
} from "@gnu-taler/taler-util/http";
import {
  FailCasesByMethod,
  ResultByMethod,
  opEmptySuccess,
  opFixedSuccess,
  opSuccessFromHttp,
  opUnknownHttpFailure,
} from "../operation.js";
import { WithdrawalOperationStatusFlag } from "../types-taler-bank-integration.js";
import {
  AccountPasswordChange,
  AccountReconfiguration,
  BankAccountConfirmWithdrawalRequest,
  BankAccountCreateWithdrawalRequest,
  CashoutRequest,
  ConversionRateClassInput,
  CreateTransactionRequest,
  MonitorTimeframeParam,
  RegisterAccountRequest,
  TalerCorebankConfigResponse,
  codecForAccountData,
  codecForBankAccountCreateWithdrawalResponse,
  codecForBankAccountTransactionInfo,
  codecForBankAccountTransactionsResponse,
  codecForCashoutPending,
  codecForCashoutStatusResponse,
  codecForCashouts,
  codecForConversionRateClass,
  codecForConversionRateClassResponse,
  codecForConversionRateClasses,
  codecForCoreBankConfig,
  codecForCreateTransactionResponse,
  codecForGlobalCashouts,
  codecForListBankAccountsResponse,
  codecForMonitorResponse,
  codecForPublicAccountsResponse,
  codecForRegisterAccountResponse,
  codecForWithdrawalPublicInfo,
} from "../types-taler-corebank.js";
import {
  ChallengeRequestResponse,
  ChallengeSolveRequest,
  codecForChallengeRequestResponse,
  codecForChallengeResponse,
} from "../types-taler-merchant.js";
import {
  CacheEvictor,
  addLongPollingParam,
  addPaginationParams,
  makeBasicAuthHeader,
  makeBearerTokenAuthHeader,
  nullEvictor,
} from "./utils.js";

export type TalerCoreBankResultByMethod<
  prop extends keyof TalerCoreBankHttpClient,
> = ResultByMethod<TalerCoreBankHttpClient, prop>;
export type TalerCoreBankErrorsByMethod<
  prop extends keyof TalerCoreBankHttpClient,
> = FailCasesByMethod<TalerCoreBankHttpClient, prop>;

export enum TalerCoreBankCacheEviction {
  DELETE_ACCOUNT,
  CREATE_ACCOUNT,
  UPDATE_ACCOUNT,
  UPDATE_PASSWORD,
  CREATE_TRANSACTION,
  CONFIRM_WITHDRAWAL,
  ABORT_WITHDRAWAL,
  CREATE_WITHDRAWAL,
  CREATE_CASHOUT,
  CREATE_CONVERSION_RATE_CLASS,
  UPDATE_CONVERSION_RATE_CLASS,
  DELETE_CONVERSION_RATE_CLASS,
}

export type Credentials = BasicCredentials | BearerCredentials;
export type BasicCredentials = {
  type: "basic";
  password: string;
};
export type BearerCredentials = {
  type: "bearer";
  accessToken: AccessToken;
};
/**
 * Protocol version spoken with the core bank.
 *
 * Endpoint must be ordered in the same way that in the docs
 * Response code (http and taler) must have the same order that in the docs
 * That way is easier to see changes
 *
 * Uses libtool's current:revision:age versioning.
 */
export class TalerCoreBankHttpClient {
  public static readonly PROTOCOL_VERSION = "10:0:2";

  httpLib: HttpRequestLibrary;
  cacheEvictor: CacheEvictor<TalerCoreBankCacheEviction>;
  constructor(
    readonly baseUrl: string,
    httpClient?: HttpRequestLibrary,
    cacheEvictor?: CacheEvictor<TalerCoreBankCacheEviction>,
  ) {
    this.httpLib = httpClient ?? createPlatformHttpLib();
    this.cacheEvictor = cacheEvictor ?? nullEvictor;
  }

  static isCompatible(version: string): boolean {
    const compare = LibtoolVersion.compare(this.PROTOCOL_VERSION, version);
    return compare?.compatible ?? false;
  }

  /**
   *
   * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-token
   */
  async createAccessToken(
    username: string,
    cred: Credentials,
    body: TokenRequest,
    params: { challengeIds?: string[] } = {},
  ) {
    const url = new URL(`accounts/${username}/token`, this.baseUrl);

    const headers: Record<string, string> = {};
    switch (cred.type) {
      case "basic": {
        headers.Authorization = makeBasicAuthHeader(username, cred.password);
        break;
      }
      case "bearer": {
        headers.Authorization = makeBearerTokenAuthHeader(cred.accessToken);
        break;
      }
      default: {
        assertUnreachable(cred);
      }
    }
    if (params.challengeIds && params.challengeIds.length > 0) {
      headers["Taler-Challenge-Ids"] = params.challengeIds.join(", ");
    }
    const resp = await this.httpLib.fetch(url.href, {
      method: "POST",
      headers,
      body,
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForTokenSuccessResponse());
      case HttpStatusCode.Accepted:
        return opKnownAlternativeHttpFailure(
          resp,
          resp.status,
          codecForChallengeResponse(),
        );
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Forbidden: {
        const details = await readTalerErrorResponse(resp);
        switch (details.code) {
          case TalerErrorCode.GENERIC_FORBIDDEN:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_ACCOUNT_LOCKED:
            return opKnownTalerFailure(details.code, details);
          default:
            return opUnknownHttpFailure(resp, details);
        }
      }
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * @deprecated use createAccessToken
   * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-token
   */
  async createAccessTokenBasic(
    username: string,
    password: string,
    body: TokenRequest,
  ) {
    return this.createAccessToken(username, { type: "basic", password }, body);
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#delete--accounts-$USERNAME-token
   */
  async deleteAccessToken(user: string, token: AccessToken) {
    const url = new URL(`accounts/${user}/token`, this.baseUrl);
    const resp = await this.httpLib.fetch(url.href, {
      method: "DELETE",
      headers: {
        Authorization: makeBearerTokenAuthHeader(token),
      },
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opEmptySuccess();
      // FIXME: missing in docs
      case HttpStatusCode.NoContent:
        return opEmptySuccess();
      // FIXME: missing in docs
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-tokens
   *
   */
  async getAccessTokenList(user: string, pagination?: PaginationParams) {
    const url = new URL(`accounts/${user}/token`, this.baseUrl);
    addPaginationParams(url, pagination);
    const resp = await this.httpLib.fetch(url.href, {
      method: "GET",
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForTokenInfoList());
      case HttpStatusCode.NoContent:
        return opFixedSuccess({ public_accounts: [] });
      case HttpStatusCode.NotFound:
        return opFixedSuccess({ public_accounts: [] });
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#config
   *
   */
  async getConfig() {
    const url = new URL(`config`, this.baseUrl);
    const resp = await this.httpLib.fetch(url.href, {
      method: "GET",
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return carefullyParseConfig(
          "taler-corebank",
          TalerCoreBankHttpClient.PROTOCOL_VERSION,
          resp,
          codecForCoreBankConfig(),
        );
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  //
  // ACCOUNTS
  //

  /**
   * https://docs.taler.net/core/api-corebank.html#post--accounts
   *
   */
  async createAccount(
    auth: AccessToken | undefined,
    body: RegisterAccountRequest,
  ) {
    const url = new URL(`accounts`, this.baseUrl);
    const headers: Record<string, string> = {};
    if (auth) {
      headers.Authorization = makeBearerTokenAuthHeader(auth);
    }
    const resp = await this.httpLib.fetch(url.href, {
      method: "POST",
      body,
      headers: headers,
    });
    switch (resp.status) {
      case HttpStatusCode.Ok: {
        await this.cacheEvictor.notifySuccess(
          TalerCoreBankCacheEviction.CREATE_ACCOUNT,
        );
        return opSuccessFromHttp(resp, codecForRegisterAccountResponse());
      }
      case HttpStatusCode.BadRequest:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Conflict: {
        const details = await readTalerErrorResponse(resp);
        switch (details.code) {
          case TalerErrorCode.BANK_REGISTER_USERNAME_REUSE:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_REGISTER_PAYTO_URI_REUSE:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_UNALLOWED_DEBIT:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_NON_ADMIN_SET_CONVERSION_RATE_CLASS:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_NON_ADMIN_SET_TAN_CHANNEL:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_MISSING_TAN_INFO:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_PASSWORD_TOO_SHORT:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_PASSWORD_TOO_LONG:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_CONVERSION_RATE_CLASS_UNKNOWN:
            return opKnownTalerFailure(details.code, details);
          default:
            return opUnknownHttpFailure(resp, details);
        }
      }
      default:
        return opUnknownHttpFailure(resp);
    }
  }
  /**
   * https://docs.taler.net/core/api-corebank.html#delete--accounts-$USERNAME
   *
   */
  async deleteAccount(
    auth: UserAndToken,
    params: { challengeIds?: string[] } = {},
  ) {
    const url = new URL(`accounts/${auth.username}`, this.baseUrl);

    const headers: Record<string, string> = {};
    headers.Authorization = makeBearerTokenAuthHeader(auth.token);
    if (params.challengeIds && params.challengeIds.length > 0) {
      headers["Taler-Challenge-Ids"] = params.challengeIds.join(", ");
    }
    const resp = await this.httpLib.fetch(url.href, {
      method: "DELETE",
      headers,
    });
    switch (resp.status) {
      case HttpStatusCode.Accepted:
        await this.cacheEvictor.notifySuccess(
          TalerCoreBankCacheEviction.DELETE_ACCOUNT,
        );
        return opKnownAlternativeHttpFailure(
          resp,
          resp.status,
          codecForChallengeResponse(),
        );
      case HttpStatusCode.NoContent:
        return opEmptySuccess();
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Conflict: {
        const details = await readTalerErrorResponse(resp);
        switch (details.code) {
          case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_ACCOUNT_BALANCE_NOT_ZERO:
            return opKnownTalerFailure(details.code, details);
          default:
            return opUnknownHttpFailure(resp, details);
        }
      }
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#patch--accounts-$USERNAME
   *
   */
  async updateAccount(
    auth: UserAndToken,
    body: AccountReconfiguration,
    params: { challengeIds?: string[] } = {},
  ) {
    const url = new URL(`accounts/${auth.username}`, this.baseUrl);
    const headers: Record<string, string> = {};
    headers.Authorization = makeBearerTokenAuthHeader(auth.token);

    if (params.challengeIds && params.challengeIds.length > 0) {
      headers["Taler-Challenge-Ids"] = params.challengeIds.join(", ");
    }

    const resp = await this.httpLib.fetch(url.href, {
      method: "PATCH",
      body,
      headers,
    });
    switch (resp.status) {
      case HttpStatusCode.Accepted:
        return opKnownAlternativeHttpFailure(
          resp,
          resp.status,
          codecForChallengeResponse(),
        );
      case HttpStatusCode.NoContent:
        await this.cacheEvictor.notifySuccess(
          TalerCoreBankCacheEviction.UPDATE_ACCOUNT,
        );
        return opEmptySuccess();
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Conflict: {
        const details = await readTalerErrorResponse(resp);
        switch (details.code) {
          case TalerErrorCode.BANK_NON_ADMIN_PATCH_LEGAL_NAME:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_NON_ADMIN_PATCH_CASHOUT:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_NON_ADMIN_SET_CONVERSION_RATE_CLASS:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_MISSING_TAN_INFO:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_PASSWORD_TOO_SHORT:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_PASSWORD_TOO_LONG:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_CONVERSION_RATE_CLASS_UNKNOWN:
            return opKnownTalerFailure(details.code, details);
          default:
            return opUnknownHttpFailure(resp, details);
        }
      }
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#patch--accounts-$USERNAME-auth
   *
   */
  async updatePassword(
    auth: UserAndToken,
    body: AccountPasswordChange,
    params: { challengeIds?: string[] } = {},
  ) {
    const url = new URL(`accounts/${auth.username}/auth`, this.baseUrl);
    const headers: Record<string, string> = {};
    headers.Authorization = makeBearerTokenAuthHeader(auth.token);

    if (params.challengeIds && params.challengeIds.length > 0) {
      headers["Taler-Challenge-Ids"] = params.challengeIds.join(", ");
    }
    const resp = await this.httpLib.fetch(url.href, {
      method: "PATCH",
      body,
      headers,
    });
    switch (resp.status) {
      case HttpStatusCode.Accepted:
        return opKnownAlternativeHttpFailure(
          resp,
          resp.status,
          codecForChallengeResponse(),
        );
      case HttpStatusCode.NoContent:
        return opEmptySuccess();
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Forbidden:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Conflict: {
        const details = await readTalerErrorResponse(resp);
        switch (details.code) {
          case TalerErrorCode.BANK_NON_ADMIN_PATCH_MISSING_OLD_PASSWORD:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_PATCH_BAD_OLD_PASSWORD:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_PASSWORD_TOO_SHORT:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_PASSWORD_TOO_LONG:
            return opKnownTalerFailure(details.code, details);
          default:
            return opUnknownHttpFailure(resp, details);
        }
      }
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#get--public-accounts
   *
   */
  async getPublicAccounts(
    filter: { account?: string } = {},
    pagination?: PaginationParams,
  ) {
    const url = new URL(`public-accounts`, this.baseUrl);
    addPaginationParams(url, pagination);
    if (filter.account !== undefined) {
      url.searchParams.set("filter_name", filter.account);
    }
    const resp = await this.httpLib.fetch(url.href, {
      method: "GET",
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForPublicAccountsResponse());
      case HttpStatusCode.NoContent:
        return opFixedSuccess({ public_accounts: [] });
      case HttpStatusCode.NotFound:
        return opFixedSuccess({ public_accounts: [] });
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#get--accounts
   *
   */
  async listAccounts(
    auth: AccessToken,
    params?: PaginationParams & { account?: string; conversionRateId?: number },
  ) {
    const url = new URL(`accounts`, this.baseUrl);
    addPaginationParams(url, params);
    if (params?.account !== undefined) {
      url.searchParams.set("filter_name", params.account);
    }
    if (params?.conversionRateId !== undefined) {
      url.searchParams.set(
        "conversion_rate_class_id",
        String(params.conversionRateId),
      );
    }
    const resp = await this.httpLib.fetch(url.href, {
      method: "GET",
      headers: {
        Authorization: makeBearerTokenAuthHeader(auth),
      },
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForListBankAccountsResponse());
      case HttpStatusCode.NoContent:
        return opFixedSuccess({ accounts: [] });
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME
   *
   */
  async getAccount(auth: UserAndToken) {
    const url = new URL(`accounts/${auth.username}`, this.baseUrl);
    const resp = await this.httpLib.fetch(url.href, {
      method: "GET",
      headers: {
        Authorization: makeBearerTokenAuthHeader(auth.token),
      },
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForAccountData());
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  //
  // TRANSACTIONS
  //

  /**
   * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-transactions
   *
   */
  async getTransactions(
    auth: UserAndToken,
    params?: PaginationParams & LongPollParams,
  ) {
    const url = new URL(`accounts/${auth.username}/transactions`, this.baseUrl);
    addPaginationParams(url, params);
    addLongPollingParam(url, params);
    const resp = await this.httpLib.fetch(url.href, {
      method: "GET",
      headers: {
        Authorization: makeBearerTokenAuthHeader(auth.token),
      },
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(
          resp,
          codecForBankAccountTransactionsResponse(),
        );
      case HttpStatusCode.NoContent:
        return opFixedSuccess({ transactions: [] });
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-transactions-$TRANSACTION_ID
   *
   */
  async getTransactionById(auth: UserAndToken, txid: number) {
    const url = new URL(
      `accounts/${auth.username}/transactions/${String(txid)}`,
      this.baseUrl,
    );
    const resp = await this.httpLib.fetch(url.href, {
      method: "GET",
      headers: {
        Authorization: makeBearerTokenAuthHeader(auth.token),
      },
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForBankAccountTransactionInfo());
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-transactions
   *
   */
  async createTransaction(
    auth: UserAndToken,
    body: CreateTransactionRequest,
    params: { challengeIds?: string[] } = {},
  ) {
    const url = new URL(`accounts/${auth.username}/transactions`, this.baseUrl);
    const headers: Record<string, string> = {};
    headers.Authorization = makeBearerTokenAuthHeader(auth.token);
    if (params.challengeIds && params.challengeIds.length > 0) {
      headers["Taler-Challenge-Ids"] = params.challengeIds.join(", ");
    }
    const resp = await this.httpLib.fetch(url.href, {
      method: "POST",
      headers,
      body,
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        await this.cacheEvictor.notifySuccess(
          TalerCoreBankCacheEviction.CREATE_TRANSACTION,
        );
        return opSuccessFromHttp(resp, codecForCreateTransactionResponse());
      case HttpStatusCode.Accepted:
        return opKnownAlternativeHttpFailure(
          resp,
          resp.status,
          codecForChallengeResponse(),
        );
      case HttpStatusCode.BadRequest:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Conflict: {
        const details = await readTalerErrorResponse(resp);
        switch (details.code) {
          case TalerErrorCode.BANK_ADMIN_CREDITOR:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_SAME_ACCOUNT:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_UNKNOWN_CREDITOR:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_UNALLOWED_DEBIT:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_TRANSFER_REQUEST_UID_REUSED:
            return opKnownTalerFailure(details.code, details);
          default:
            return opUnknownHttpFailure(resp, details);
        }
      }
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  //
  // WITHDRAWALS
  //

  /**
   * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-withdrawals
   *
   */
  async createWithdrawal(
    auth: UserAndToken,
    body: BankAccountCreateWithdrawalRequest,
  ) {
    const url = new URL(`accounts/${auth.username}/withdrawals`, this.baseUrl);
    const resp = await this.httpLib.fetch(url.href, {
      method: "POST",
      headers: {
        Authorization: makeBearerTokenAuthHeader(auth.token),
      },
      body,
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        await this.cacheEvictor.notifySuccess(
          TalerCoreBankCacheEviction.CREATE_WITHDRAWAL,
        );
        return opSuccessFromHttp(
          resp,
          codecForBankAccountCreateWithdrawalResponse(),
        );
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Conflict:
        return opKnownHttpFailure(resp.status, resp);
      //FIXME: missing in docs
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-withdrawals-$WITHDRAWAL_ID-confirm
   *
   */
  async confirmWithdrawalById(
    auth: UserAndToken,
    body: BankAccountConfirmWithdrawalRequest,
    wid: string,
    params: { challengeIds?: string[] } = {},
  ) {
    const url = new URL(
      `accounts/${auth.username}/withdrawals/${wid}/confirm`,
      this.baseUrl,
    );
    const headers: Record<string, string> = {};
    headers.Authorization = makeBearerTokenAuthHeader(auth.token);
    if (params.challengeIds && params.challengeIds.length > 0) {
      headers["Taler-Challenge-Ids"] = params.challengeIds.join(", ");
    }
    const resp = await this.httpLib.fetch(url.href, {
      method: "POST",
      headers,
      body,
    });
    switch (resp.status) {
      case HttpStatusCode.Accepted:
        return opKnownAlternativeHttpFailure(
          resp,
          resp.status,
          codecForChallengeResponse(),
        );
      case HttpStatusCode.NoContent:
        await this.cacheEvictor.notifySuccess(
          TalerCoreBankCacheEviction.CONFIRM_WITHDRAWAL,
        );
        return opEmptySuccess();
      //FIXME: missing in docs
      case HttpStatusCode.BadRequest:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Conflict: {
        const details = await readTalerErrorResponse(resp);
        switch (details.code) {
          case TalerErrorCode.BANK_CONFIRM_ABORT_CONFLICT:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_CONFIRM_INCOMPLETE:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_UNALLOWED_DEBIT:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_AMOUNT_DIFFERS:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_AMOUNT_REQUIRED:
            return opKnownTalerFailure(details.code, details);
          default:
            return opUnknownHttpFailure(resp, details);
        }
      }
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-withdrawals-$WITHDRAWAL_ID-abort
   *
   */
  async abortWithdrawalById(auth: UserAndToken, wid: string) {
    const url = new URL(
      `accounts/${auth.username}/withdrawals/${wid}/abort`,
      this.baseUrl,
    );
    const resp = await this.httpLib.fetch(url.href, {
      method: "POST",
      headers: {
        Authorization: makeBearerTokenAuthHeader(auth.token),
      },
    });
    switch (resp.status) {
      case HttpStatusCode.NoContent:
        await this.cacheEvictor.notifySuccess(
          TalerCoreBankCacheEviction.ABORT_WITHDRAWAL,
        );
        return opEmptySuccess();
      //FIXME: missing in docs
      case HttpStatusCode.BadRequest:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Conflict:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#get--withdrawals-$WITHDRAWAL_ID
   *
   */
  async getWithdrawalById(
    wid: string,
    params?: {
      old_state?: WithdrawalOperationStatusFlag;
    } & LongPollParams,
  ) {
    const url = new URL(`withdrawals/${wid}`, this.baseUrl);
    addLongPollingParam(url, params);
    if (params) {
      url.searchParams.set(
        "old_state",
        !params.old_state ? "pending" : params.old_state,
      );
    }
    const resp = await this.httpLib.fetch(url.href, {
      method: "GET",
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForWithdrawalPublicInfo());
      //FIXME: missing in docs
      case HttpStatusCode.BadRequest:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  //
  // CASHOUTS
  //

  /**
   * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts
   *
   */
  async createCashout(
    auth: UserAndToken,
    body: CashoutRequest,
    params: { challengeIds?: string[] } = {},
  ) {
    const url = new URL(`accounts/${auth.username}/cashouts`, this.baseUrl);
    const headers: Record<string, string> = {};
    headers.Authorization = makeBearerTokenAuthHeader(auth.token);
    if (params.challengeIds && params.challengeIds.length > 0) {
      headers["Taler-Challenge-Ids"] = params.challengeIds.join(", ");
    }
    const resp = await this.httpLib.fetch(url.href, {
      method: "POST",
      headers,
      body,
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        await this.cacheEvictor.notifySuccess(
          TalerCoreBankCacheEviction.CREATE_CASHOUT,
        );
        return opSuccessFromHttp(resp, codecForCashoutPending());
      case HttpStatusCode.Accepted:
        return opKnownAlternativeHttpFailure(
          resp,
          resp.status,
          codecForChallengeResponse(),
        );
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Conflict: {
        const details = await readTalerErrorResponse(resp);
        switch (details.code) {
          case TalerErrorCode.BANK_TRANSFER_REQUEST_UID_REUSED:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_BAD_CONVERSION:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_CONVERSION_AMOUNT_TO_SMALL:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_UNALLOWED_DEBIT:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_CONFIRM_INCOMPLETE:
            return opKnownTalerFailure(details.code, details);
          default:
            return opUnknownHttpFailure(resp, details);
        }
      }
      case HttpStatusCode.BadGateway: {
        const details = await readTalerErrorResponse(resp);
        switch (details.code) {
          case TalerErrorCode.BANK_TAN_CHANNEL_SCRIPT_FAILED:
            return opKnownTalerFailure(details.code, details);
          default:
            return opUnknownHttpFailure(resp, details);
        }
      }
      case HttpStatusCode.NotImplemented:
        const details = await readTalerErrorResponse(resp);
        switch (details.code) {
          case TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED:
            return opKnownTalerFailure(details.code, details);
          default:
            return opKnownHttpFailure(resp.status, resp);
        }
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts-$CASHOUT_ID
   *
   */
  async getCashoutById(auth: UserAndToken, cid: number) {
    const url = new URL(
      `accounts/${auth.username}/cashouts/${cid}`,
      this.baseUrl,
    );
    const resp = await this.httpLib.fetch(url.href, {
      method: "GET",
      headers: {
        Authorization: makeBearerTokenAuthHeader(auth.token),
      },
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForCashoutStatusResponse());
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.NotImplemented:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts
   *
   */
  async getAccountCashouts(auth: UserAndToken, pagination?: PaginationParams) {
    const url = new URL(`accounts/${auth.username}/cashouts`, this.baseUrl);
    addPaginationParams(url, pagination);
    const resp = await this.httpLib.fetch(url.href, {
      method: "GET",
      headers: {
        Authorization: makeBearerTokenAuthHeader(auth.token),
      },
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForCashouts());
      case HttpStatusCode.NoContent:
        return opFixedSuccess({ cashouts: [] });
      case HttpStatusCode.NotImplemented:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#get--cashouts
   *
   */
  async getGlobalCashouts(auth: AccessToken, pagination?: PaginationParams) {
    const url = new URL(`cashouts`, this.baseUrl);
    addPaginationParams(url, pagination);
    const resp = await this.httpLib.fetch(url.href, {
      method: "GET",
      headers: {
        Authorization: makeBearerTokenAuthHeader(auth),
      },
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForGlobalCashouts());
      case HttpStatusCode.NoContent:
        return opFixedSuccess({ cashouts: [] });
      case HttpStatusCode.NotImplemented:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  //
  // CONVERSION RATE CLASS
  //

  /**
   * https://docs.taler.net/core/api-corebank.html#post--conversion-rate-classes
   *
   */
  async createConversionRateClass(
    auth: AccessToken,
    body: ConversionRateClassInput,
  ) {
    const url = new URL(`conversion-rate-classes`, this.baseUrl);

    const resp = await this.httpLib.fetch(url.href, {
      method: "POST",
      headers: {
        Authorization: makeBearerTokenAuthHeader(auth),
      },
      body,
    });

    switch (resp.status) {
      case HttpStatusCode.Ok:
        await this.cacheEvictor.notifySuccess(
          TalerCoreBankCacheEviction.CREATE_CONVERSION_RATE_CLASS,
        );
        return opSuccessFromHttp(resp, codecForConversionRateClassResponse());
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Forbidden:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Conflict: {
        const details = await readTalerErrorResponse(resp);
        switch (details.code) {
          case TalerErrorCode.BANK_NAME_REUSE:
            return opKnownTalerFailure(details.code, details);
          default:
            return opUnknownHttpFailure(resp, details);
        }
      }
      case HttpStatusCode.NotImplemented:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#patch--conversion-rate-classes-CLASS_ID
   *
   */
  async updateConversionRateClass(
    auth: AccessToken,
    cid: number,
    body: ConversionRateClassInput,
  ) {
    const url = new URL(`conversion-rate-classes/${cid}`, this.baseUrl);
    const resp = await this.httpLib.fetch(url.href, {
      method: "PATCH",
      headers: {
        Authorization: makeBearerTokenAuthHeader(auth),
      },
      body,
    });
    switch (resp.status) {
      case HttpStatusCode.NoContent:
        await this.cacheEvictor.notifySuccess(
          TalerCoreBankCacheEviction.UPDATE_CONVERSION_RATE_CLASS,
        );
        return opEmptySuccess();
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Forbidden:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Conflict: {
        const details = await readTalerErrorResponse(resp);
        switch (details.code) {
          case TalerErrorCode.BANK_NAME_REUSE:
            return opKnownTalerFailure(details.code, details);
          default:
            return opUnknownHttpFailure(resp, details);
        }
      }
      case HttpStatusCode.NotImplemented:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts
   *
   */
  async deleteConversionRateClass(auth: AccessToken, cid: number) {
    const url = new URL(`conversion-rate-classes/${cid}`, this.baseUrl);

    const resp = await this.httpLib.fetch(url.href, {
      method: "DELETE",
      headers: {
        Authorization: makeBearerTokenAuthHeader(auth),
      },
    });
    switch (resp.status) {
      case HttpStatusCode.NoContent:
        await this.cacheEvictor.notifySuccess(
          TalerCoreBankCacheEviction.DELETE_CONVERSION_RATE_CLASS,
        );
        return opEmptySuccess();
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Forbidden:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      // case HttpStatusCode.Conflict: {
      //   const details = await readTalerErrorResponse(resp);
      //   switch (details.code) {
      //     case TalerErrorCode.BANK_LI:
      //       return opKnownTalerFailure(details.code, details);
      //     default:
      //       return opUnknownHttpFailure(resp, details);
      //   }
      // }
      case HttpStatusCode.NotImplemented:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#get--conversion-rate-classes-CLASS_ID
   *
   */
  async getConversionRateClass(auth: AccessToken, cid: number) {
    const url = new URL(`conversion-rate-classes/${cid}`, this.baseUrl);
    const resp = await this.httpLib.fetch(url.href, {
      method: "GET",
      headers: {
        Authorization: makeBearerTokenAuthHeader(auth),
      },
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForConversionRateClass());
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Forbidden:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.NotImplemented:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#get--conversion-rate-classes
   *
   */
  async listConversionRateClasses(
    auth: AccessToken,
    params: PaginationParams & { className?: string } = {},
  ) {
    const url = new URL(`conversion-rate-classes`, this.baseUrl);
    addPaginationParams(url, params);
    if (params.className) {
      url.searchParams.set("filter_name", params.className);
    }
    const resp = await this.httpLib.fetch(url.href, {
      method: "GET",
      headers: {
        Authorization: makeBearerTokenAuthHeader(auth),
      },
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForConversionRateClasses());
      case HttpStatusCode.NoContent:
        return opFixedSuccess({ classes: [], default: {} as any });
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Forbidden:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.NotImplemented:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }
  //
  // 2FA
  //

  /**
   * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-challenge-$CHALLENGE_ID
   *
   */

  async sendChallenge(username: string, cid: string) {
    const url = new URL(`accounts/${username}/challenge/${cid}`, this.baseUrl);
    const resp = await this.httpLib.fetch(url.href, {
      method: "POST",
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForChallengeRequestResponse());
      case HttpStatusCode.NoContent:
        return opFixedSuccess<ChallengeRequestResponse>({});
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Forbidden:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.TooManyRequests:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.BadGateway: {
        const details = await readTalerErrorResponse(resp);
        switch (details.code) {
          case TalerErrorCode.BANK_TAN_CHANNEL_SCRIPT_FAILED:
            return opKnownTalerFailure(details.code, details);
          default:
            return opUnknownHttpFailure(resp, details);
        }
      }
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-challenge-$CHALLENGE_ID-confirm
   *
   */
  async confirmChallenge(
    username: string,
    cid: string,
    body: ChallengeSolveRequest,
  ) {
    const url = new URL(
      `accounts/${username}/challenge/${cid}/confirm`,
      this.baseUrl,
    );
    const resp = await this.httpLib.fetch(url.href, {
      method: "POST",
      body,
    });
    switch (resp.status) {
      case HttpStatusCode.NoContent:
        return opEmptySuccess();
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Conflict: {
        const details = await readTalerErrorResponse(resp);
        switch (details.code) {
          case TalerErrorCode.BANK_TAN_CHALLENGE_FAILED:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_TAN_CHALLENGE_EXPIRED:
            return opKnownTalerFailure(details.code, details);
          default:
            return opUnknownHttpFailure(resp, details);
        }
      }
      case HttpStatusCode.NotFound: {
        const details = await readTalerErrorResponse(resp);
        switch (details.code) {
          case TalerErrorCode.BANK_TRANSACTION_NOT_FOUND:
            return opKnownTalerFailure(details.code, details);
          case TalerErrorCode.BANK_TAN_CHALLENGE_EXPIRED:
            return opKnownTalerFailure(details.code, details);
          default:
            return opUnknownHttpFailure(resp, details);
        }
      }
      case HttpStatusCode.TooManyRequests: {
        return opKnownHttpFailure(resp.status, resp);
      }
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  //
  // MONITOR
  //

  /**
   * https://docs.taler.net/core/api-corebank.html#get--monitor
   *
   */
  async getMonitor(
    auth: AccessToken,
    params: {
      timeframe?: MonitorTimeframeParam;
      date?: AbsoluteTime;
    } = {},
  ) {
    const url = new URL(`monitor`, this.baseUrl);
    if (params.timeframe) {
      url.searchParams.set(
        "timeframe",
        MonitorTimeframeParam[params.timeframe],
      );
    }
    if (params.date) {
      const { t_s: seconds } = AbsoluteTime.toProtocolTimestamp(params.date);
      if (seconds !== "never") {
        url.searchParams.set("date_s", String(seconds));
      }
    }
    const resp = await this.httpLib.fetch(url.href, {
      method: "GET",
      headers: {
        Authorization: makeBearerTokenAuthHeader(auth),
      },
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForMonitorResponse());
      case HttpStatusCode.BadRequest:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  //
  // Others API
  //

  /**
   * https://docs.taler.net/core/api-corebank.html#taler-bank-integration-api
   *
   */
  getIntegrationAPI(): URL {
    return new URL(`taler-integration/`, this.baseUrl);
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#taler-bank-integration-api
   *
   */
  getWireGatewayAPI(username: string): URL {
    return new URL(`accounts/${username}/taler-wire-gateway/`, this.baseUrl);
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#taler-bank-integration-api
   *
   */
  getRevenueAPI(username: string): URL {
    return new URL(`accounts/${username}/taler-revenue/`, this.baseUrl);
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#any--accounts-$USERNAME-conversion-info-*
   *
   */
  getConversionInfoAPIForUser(username: string): URL {
    return new URL(`accounts/${username}/conversion-info/`, this.baseUrl);
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#any--conversion-rate-classes-$CLASS_ID-conversion-info-*
   *
   */
  getConversionInfoAPIForClass(classId: number): URL {
    return new URL(
      `conversion-rate-classes/${String(classId)}/conversion-info/`,
      this.baseUrl,
    );
  }

  /**
   * https://docs.taler.net/core/api-corebank.html#any--conversion-info-*
   *
   */
  getConversionInfoAPI(): URL {
    return new URL(`conversion-info/`, this.baseUrl);
  }
}
