/*
 This file is part of GNU Taler
 (C) 2024 Taler Systems S.A.

 GNU Taler is free software; you can redistribute it and/or modify it under the
 terms of the GNU Affero 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 Affero General Public License along with
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>

 SPDX-License-Identifier: AGPL-3.0-or-later
 */

import {
  Codec,
  buildCodecForObject,
  buildCodecForUnion,
  codecForBoolean,
  codecForConstString,
  codecForEither,
  codecForList,
  codecForNumber,
  codecForString,
  codecForStringURL,
  codecOptional,
} from "./codec.js";
import { TalerWireGatewayApi } from "./index.js";
import { codecForAmountString } from "./amounts.js";
import { PaytoString, codecForPaytoString } from "./payto.js";
import { codecForTimestamp } from "./time.js";
import {
  AmountString,
  EddsaPublicKey,
  HashCode,
  SafeUint64,
  ShortHashCode,
  Timestamp,
  WadId,
  codecForEddsaPublicKey,
} from "./types-taler-common.js";

export interface TransferResponse {
  // Timestamp that indicates when the wire transfer will be executed.
  // In cases where the wire transfer gateway is unable to know when
  // the wire transfer will be executed, the time at which the request
  // has been received and stored will be returned.
  // The purpose of this field is for debugging (humans trying to find
  // the transaction) as well as for taxation (determining which
  // time period a transaction belongs to).
  timestamp: Timestamp;

  // Opaque ID of the transaction that the bank has made.
  row_id: SafeUint64;
}

export interface WireConfig {
  // Name of the API.
  name: "taler-wire-gateway";

  // libtool-style representation of the Bank protocol version, see
  // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
  // The format is "current:revision:age".
  version: string;

  // Currency used by this gateway.
  currency: string;

  // URN of the implementation (needed to interpret 'revision' in version).
  // @since v0, may become mandatory in the future.
  implementation?: string;

  // Wether implementation support account existence check
  support_account_check: boolean;
}

export interface TransferRequest {
  // Nonce to make the request idempotent.  Requests with the same
  // request_uid that differ in any of the other fields
  // are rejected.
  request_uid: HashCode;

  // Amount to transfer.
  amount: AmountString;

  // Base URL of the exchange.  Shall be included by the bank gateway
  // in the appropriate section of the wire transfer details.
  exchange_base_url: string;

  // Wire transfer identifier chosen by the exchange,
  // used by the merchant to identify the Taler order(s)
  // associated with this wire transfer.
  wtid: ShortHashCode;

  // The recipient's account identifier as a payto URI.
  credit_account: PaytoString;
}

export interface IncomingHistory {
  // Array of incoming transactions.
  incoming_transactions: IncomingBankTransaction[];

  // Payto URI to identify the receiver of funds.
  // This must be one of the exchange's bank accounts.
  // Credit account is shared by all incoming transactions
  // as per the nature of the request.

  // undefined if incoming transaction is empty
  credit_account?: PaytoString;
}

// Union discriminated by the "type" field.
export type IncomingBankTransaction =
  | IncomingKycAuthTransaction
  | IncomingReserveTransaction
  | IncomingWadTransaction;

export interface IncomingReserveTransaction {
  type: "RESERVE";

  // Opaque identifier of the returned record.
  row_id: SafeUint64;

  // Date of the transaction.
  date: Timestamp;

  // Amount transferred.
  amount: AmountString;

  // Payto URI to identify the sender of funds.
  debit_account: PaytoString;

  // The reserve public key extracted from the transaction details.
  reserve_pub: EddsaPublicKey;
}

export interface IncomingKycAuthTransaction {
  type: "KYCAUTH";

  // Opaque identifier of the returned record.
  row_id: SafeUint64;

  // Date of the transaction.
  date: Timestamp;

  // Amount transferred.
  amount: AmountString;

  // Payto URI to identify the sender of funds.
  debit_account: PaytoString;

  // The reserve public key extracted from the transaction details.
  account_pub: EddsaPublicKey;
}

export interface IncomingWadTransaction {
  type: "WAD";

  // Opaque identifier of the returned record.
  row_id: SafeUint64;

  // Date of the transaction.
  date: Timestamp;

  // Amount transferred.
  amount: AmountString;

  // Payto URI to identify the sender of funds.
  debit_account: PaytoString;

  // Base URL of the exchange that originated the wad.
  origin_exchange_url: string;

  // The reserve public key extracted from the transaction details.
  wad_id: WadId;
}

export interface OutgoingHistory {
  // Array of outgoing transactions.
  outgoing_transactions: OutgoingBankTransaction[];

  // Payto URI to identify the sender of funds.
  // This must be one of the exchange's bank accounts.
  // Credit account is shared by all incoming transactions
  // as per the nature of the request.

  // undefined if outgoing transactions is empty
  debit_account?: PaytoString;
}

export interface OutgoingBankTransaction {
  // Opaque identifier of the returned record.
  row_id: SafeUint64;

  // Date of the transaction.
  date: Timestamp;

  // Amount transferred.
  amount: AmountString;

  // Payto URI to identify the receiver of funds.
  credit_account: PaytoString;

  // The wire transfer ID in the outgoing transaction.
  wtid: ShortHashCode;

  // Base URL of the exchange.
  exchange_base_url: string;
}

export interface AddIncomingRequest {
  // Amount to transfer.
  amount: AmountString;

  // Reserve public key that is included in the wire transfer details
  // to identify the reserve that is being topped up.
  reserve_pub: EddsaPublicKey;

  // Account (as payto URI) that makes the wire transfer to the exchange.
  // Usually this account must be created by the test harness before this API is
  // used.  An exception is the "exchange-fakebank", where any debit account can be
  // specified, as it is automatically created.
  debit_account: PaytoString;
}

export interface AddKycauthRequest {
  // Amount to transfer.
  amount: AmountString;

  // Account public key that is included in the wire transfer details
  // to associate this key with the originating bank account.
  account_pub: EddsaPublicKey;

  // Account (as payto URI) that makes the wire transfer to the exchange.
  // Usually this account must be created by the test harness before this
  // API is used. An exception is the "fakebank", where any debit account
  // can be specified, as it is automatically created.
  debit_account: string;
}

export interface AddIncomingResponse {
  // Timestamp that indicates when the wire transfer will be executed.
  // In cases where the wire transfer gateway is unable to know when
  // the wire transfer will be executed, the time at which the request
  // has been received and stored will be returned.
  // The purpose of this field is for debugging (humans trying to find
  // the transaction) as well as for taxation (determining which
  // time period a transaction belongs to).
  timestamp: Timestamp;

  // Opaque ID of the transaction that the bank has made.
  row_id: SafeUint64;
}

export interface BankWireTransferList {
  // Array of initiated transfers.
  transfers: BankWireTransferListStatus[];

  // Payto URI to identify the sender of funds.
  // This must be one of the exchange's bank accounts.
  // Credit account is shared by all incoming transactions
  // as per the nature of the request.
  debit_account: string;
}

export type WireTransferStatus =
  | "pending"
  | "transient_failure"
  | "permanent_failure"
  | "success";

export interface BankWireTransferListStatus {
  // Opaque ID of the wire transfer initiation performed by the bank.
  // It is different from the /history endpoints row_id.
  row_id: SafeUint64;

  // Current status of the transfer
  // pending: the transfer is in progress
  // transient_failure: the transfer has failed but may succeed later
  // permanent_failure: the transfer has failed permanently and will never appear in the outgoing history
  // success: the transfer has succeeded  and appears in the outgoing history
  status: WireTransferStatus;

  // Amount to transfer.
  amount: AmountString;

  // The recipient's account identifier as a payto URI.
  credit_account: string;

  // Timestamp that indicates when the wire transfer was executed.
  // In cases where the wire transfer gateway is unable to know when
  // the wire transfer will be executed, the time at which the request
  // has been received and stored will be returned.
  // The purpose of this field is for debugging (humans trying to find
  // the transaction) as well as for taxation (determining which
  // time period a transaction belongs to).
  timestamp: Timestamp;
}

export interface BankWireTransferStatus {
  // Current status of the transfer
  // pending: the transfer is in progress
  // transient_failure: the transfer has failed but may succeed later
  // permanent_failure: the transfer has failed permanently and will never appear in the outgoing history
  // success: the transfer has succeeded  and appears in the outgoing history
  status: WireTransferStatus;

  // Optional unstructured messages about the transfer's status. Can be used to document the reasons for failure or the state of progress.
  status_msg?: string;

  // Amount to transfer.
  amount: AmountString;

  // Base URL of the exchange.  Shall be included by the bank gateway
  // in the appropriate section of the wire transfer details.
  exchange_base_url: string;

  // Wire transfer identifier chosen by the exchange,
  // used by the merchant to identify the Taler order(s)
  // associated with this wire transfer.
  wtid: ShortHashCode;

  // The recipient's account identifier as a payto URI.
  credit_account: string;

  // Timestamp that indicates when the wire transfer was executed.
  // In cases where the wire transfer gateway is unable to know when
  // the wire transfer will be executed, the time at which the request
  // has been received and stored will be returned.
  // The purpose of this field is for debugging (humans trying to find
  // the transaction) as well as for taxation (determining which
  // time period a transaction belongs to).
  timestamp: Timestamp;
}

export const codecForWireConfigResponse =
  (): Codec<TalerWireGatewayApi.WireConfig> =>
    buildCodecForObject<TalerWireGatewayApi.WireConfig>()
      .property("currency", codecForString())
      .property("implementation", codecForString())
      .property("name", codecForConstString("taler-wire-gateway"))
      .property("support_account_check", codecForBoolean())
      .property("version", codecForString())
      .build("TalerWireGatewayApi.WireConfig");

export const codecForTransferResponse =
  (): Codec<TalerWireGatewayApi.TransferResponse> =>
    buildCodecForObject<TalerWireGatewayApi.TransferResponse>()
      .property("row_id", codecForNumber())
      .property("timestamp", codecForTimestamp)
      .build("TalerWireGatewayApi.TransferResponse");

export const codecForIncomingHistory =
  (): Codec<TalerWireGatewayApi.IncomingHistory> =>
    buildCodecForObject<TalerWireGatewayApi.IncomingHistory>()
      .property("credit_account", codecForPaytoString())
      .property(
        "incoming_transactions",
        codecForList(codecForIncomingBankTransaction()),
      )
      .build("TalerWireGatewayApi.IncomingHistory");

export const codecForIncomingBankTransaction =
  (): Codec<TalerWireGatewayApi.IncomingBankTransaction> =>
    buildCodecForUnion<TalerWireGatewayApi.IncomingBankTransaction>()
      .discriminateOn("type")
      .alternative("RESERVE", codecForIncomingReserveTransaction())
      .alternative("KYCAUTH", codecForIncomingKycAuthTransaction())
      .alternative("WAD", codecForIncomingWadTransaction())
      .build("TalerWireGatewayApi.IncomingBankTransaction");

export const codecForIncomingReserveTransaction =
  (): Codec<TalerWireGatewayApi.IncomingReserveTransaction> =>
    buildCodecForObject<TalerWireGatewayApi.IncomingReserveTransaction>()
      .property("amount", codecForAmountString())
      .property("date", codecForTimestamp)
      .property("debit_account", codecForPaytoString())
      .property("reserve_pub", codecForEddsaPublicKey())
      .property("row_id", codecForNumber())
      .property("type", codecForConstString("RESERVE"))
      .build("TalerWireGatewayApi.IncomingReserveTransaction");

export const codecForIncomingKycAuthTransaction =
  (): Codec<TalerWireGatewayApi.IncomingKycAuthTransaction> =>
    buildCodecForObject<TalerWireGatewayApi.IncomingKycAuthTransaction>()
      .property("amount", codecForAmountString())
      .property("date", codecForTimestamp)
      .property("debit_account", codecForPaytoString())
      .property("account_pub", codecForEddsaPublicKey())
      .property("row_id", codecForNumber())
      .property("type", codecForConstString("KYCAUTH"))
      .build("TalerWireGatewayApi.IncomingKycAuthTransaction");

export const codecForIncomingWadTransaction =
  (): Codec<TalerWireGatewayApi.IncomingWadTransaction> =>
    buildCodecForObject<TalerWireGatewayApi.IncomingWadTransaction>()
      .property("amount", codecForAmountString())
      .property("date", codecForTimestamp)
      .property("debit_account", codecForPaytoString())
      .property("origin_exchange_url", codecForString())
      .property("row_id", codecForNumber())
      .property("type", codecForConstString("WAD"))
      .property("wad_id", codecForString())
      .build("TalerWireGatewayApi.IncomingWadTransaction");

export const codecForOutgoingHistory =
  (): Codec<TalerWireGatewayApi.OutgoingHistory> =>
    buildCodecForObject<TalerWireGatewayApi.OutgoingHistory>()
      .property("debit_account", codecForPaytoString())
      .property(
        "outgoing_transactions",
        codecForList(codecForOutgoingBankTransaction()),
      )
      .build("TalerWireGatewayApi.OutgoingHistory");

export const codecForOutgoingBankTransaction =
  (): Codec<TalerWireGatewayApi.OutgoingBankTransaction> =>
    buildCodecForObject<TalerWireGatewayApi.OutgoingBankTransaction>()
      .property("row_id", codecForNumber())
      .property("date", codecForTimestamp)
      .property("amount", codecForAmountString())
      .property("credit_account", codecForPaytoString())
      .property("wtid", codecForString())
      .property("exchange_base_url", codecForString())
      .build("TalerWireGatewayApi.OutgoingBankTransaction");

export const codecForAddIncomingResponse =
  (): Codec<TalerWireGatewayApi.AddIncomingResponse> =>
    buildCodecForObject<TalerWireGatewayApi.AddIncomingResponse>()
      .property("row_id", codecForNumber())
      .property("timestamp", codecForTimestamp)
      .build("TalerWireGatewayApi.AddIncomingResponse");

export const codecForBankWireTransferList =
  (): Codec<TalerWireGatewayApi.BankWireTransferList> =>
    buildCodecForObject<TalerWireGatewayApi.BankWireTransferList>()
      .property("debit_account", codecForPaytoString())
      .property("transfers", codecForList(codecForBankWireTransferListStatus()))
      .build("TalerWireGatewayApi.BankWireTransferList");

export const codecForBankWireTransferStatus =
  (): Codec<TalerWireGatewayApi.BankWireTransferStatus> =>
    buildCodecForObject<TalerWireGatewayApi.BankWireTransferStatus>()
      .property(
        "status",
        codecForEither(
          codecForConstString("pending"),
          codecForConstString("transient_failure"),
          codecForConstString("permanent_failure"),
          codecForConstString("success"),
        ),
      )
      .property("status_msg", codecOptional(codecForString()))
      .property("amount", codecForAmountString())
      .property("exchange_base_url", codecForStringURL())
      .property("wtid", codecForString())
      .property("credit_account", codecForPaytoString())
      .property("timestamp", codecForTimestamp)
      .build("TalerWireGatewayApi.BankWireTransferStatus");

export const codecForBankWireTransferListStatus =
  (): Codec<TalerWireGatewayApi.BankWireTransferListStatus> =>
    buildCodecForObject<TalerWireGatewayApi.BankWireTransferListStatus>()
      .property("row_id", codecForNumber())
      .property(
        "status",
        codecForEither(
          codecForConstString("pending"),
          codecForConstString("transient_failure"),
          codecForConstString("permanent_failure"),
          codecForConstString("success"),
        ),
      )
      .property("amount", codecForAmountString())
      .property("credit_account", codecForPaytoString())
      .property("timestamp", codecForTimestamp)
      .build("TalerWireGatewayApi.BankWireTransferListStatus");
