/*
 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 { HttpRequestLibrary, makeBasicAuthHeader } from "../http-common.js";
import { HttpStatusCode } from "../http-status-codes.js";
import { createPlatformHttpLib } from "../http.js";
import {
  FailCasesByMethod,
  ResultByMethod,
  opFixedSuccess,
  opKnownHttpFailure,
  opSuccessFromHttp,
  opUnknownHttpFailure,
} from "../operation.js";
import {
  codecForAddIncomingResponse,
  codecForBankWireTransferList,
  codecForIncomingHistory,
  codecForOutgoingHistory,
  codecForTransferResponse,
} from "../types-taler-wire-gateway.js";
import { addLongPollingParam, addPaginationParams } from "./utils.js";

import { LongPollParams, PaginationParams } from "../types-taler-common.js";
import * as TalerWireGatewayApi from "../types-taler-wire-gateway.js";
import { carefullyParseConfig, LibtoolVersion } from "../index.js";

export type TalerWireGatewayResultByMethod<
  prop extends keyof TalerWireGatewayHttpClient,
> = ResultByMethod<TalerWireGatewayHttpClient, prop>;
export type TalerWireGatewayErrorsByMethod<
  prop extends keyof TalerWireGatewayHttpClient,
> = FailCasesByMethod<TalerWireGatewayHttpClient, prop>;

export interface TalerWireGatewayAuth {
  username: string;
  password: string;
}

/**
 * The API is used by the exchange to trigger transactions and query
 * incoming transactions, as well as by the auditor to query incoming
 * and outgoing transactions.
 *
 * https://docs.taler.net/core/api-bank-wire.html
 */
export class TalerWireGatewayHttpClient {
  httpLib: HttpRequestLibrary;
  public static readonly PROTOCOL_VERSION = "4:0:0";

  constructor(
    readonly baseUrl: string,
    options: {
      httpClient?: HttpRequestLibrary;
    } = {},
  ) {
    this.httpLib = options.httpClient ?? createPlatformHttpLib();
  }

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

  /**
   * https://docs.taler.net/core/api-bank-wire.html#get--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-wire-gateway",
          TalerWireGatewayHttpClient.PROTOCOL_VERSION,
          resp,
          TalerWireGatewayApi.codecForWireConfigResponse(),
        );
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-bank-wire.html#post--transfer
   *
   */
  async makeWireTransfer(req: {
    body: TalerWireGatewayApi.TransferRequest;
    auth: { username: string; password: string };
  }) {
    const url = new URL(`transfer`, this.baseUrl);
    const resp = await this.httpLib.fetch(url.href, {
      method: "POST",
      headers: {
        Authorization: makeBasicAuthHeader(
          req.auth.username,
          req.auth.password,
        ),
      },
      body: req.body,
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForTransferResponse());
      //FIXME: show more details in docs
      case HttpStatusCode.BadRequest:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      //FIXME: show more details in docs
      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-bank-wire.html#get--transfers
   *
   */
  async getTransfers(req: {
    params?: {
      status?: TalerWireGatewayApi.WireTransferStatus;
    } & PaginationParams;
    auth: TalerWireGatewayAuth;
  }) {
    const url = new URL(`transfers`, this.baseUrl);
    if (req.params) {
      if (req.params.status) {
        url.searchParams.set("status", req.params.status);
      }
    }
    addPaginationParams(url, req.params);
    const resp = await this.httpLib.fetch(url.href, {
      method: "GET",
      headers: {
        Authorization: makeBasicAuthHeader(
          req.auth.username,
          req.auth.password,
        ),
      },
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForBankWireTransferList());
      // FIXME: account should not be returned or make it optional
      case HttpStatusCode.NoContent:
        return opFixedSuccess({
          transfers: [],
          debit_account: undefined,
        });
      // FIXME: show more details in docs
      case HttpStatusCode.BadRequest:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      // FIXME: show more details in docs
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-bank-wire.html#get--transfers-$ROW_ID
   *
   */
  async getTransferStatus(req: { auth: TalerWireGatewayAuth; rowId?: number }) {
    const url = new URL(`transfers/${req.rowId}`, this.baseUrl);
    const resp = await this.httpLib.fetch(url.href, {
      method: "GET",
      headers: {
        Authorization: makeBasicAuthHeader(
          req.auth.username,
          req.auth.password,
        ),
      },
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForBankWireTransferList());
      //FIXME: account should not be returned or make it optional
      case HttpStatusCode.BadRequest:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      //FIXME: show more details in docs
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-bank-wire.html#get--history-incoming
   *
   */
  async getHistoryIncoming(req: {
    params?: PaginationParams & LongPollParams;
    auth: TalerWireGatewayAuth;
  }) {
    const url = new URL(`history/incoming`, this.baseUrl);
    addPaginationParams(url, req.params);
    addLongPollingParam(url, req.params);
    const resp = await this.httpLib.fetch(url.href, {
      method: "GET",
      headers: {
        Authorization: makeBasicAuthHeader(
          req.auth.username,
          req.auth.password,
        ),
      },
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForIncomingHistory());
      //FIXME: account should not be returned or make it optional
      case HttpStatusCode.NoContent:
        return opFixedSuccess({
          incoming_transactions: [],
          credit_account: undefined,
        });
      //FIXME: show more details in docs
      case HttpStatusCode.BadRequest:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      //FIXME: show more details in docs
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-bank-wire.html#get--history-outgoing
   *
   */
  async getHistoryOutgoing(req: {
    auth: TalerWireGatewayAuth;
    params?: PaginationParams & LongPollParams;
  }) {
    const url = new URL(`history/outgoing`, this.baseUrl);
    addPaginationParams(url, req.params);
    addLongPollingParam(url, req.params);
    const resp = await this.httpLib.fetch(url.href, {
      method: "GET",
      headers: {
        Authorization: makeBasicAuthHeader(
          req.auth.username,
          req.auth.password,
        ),
      },
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForOutgoingHistory());
      //FIXME: account should not be returned or make it optional
      case HttpStatusCode.NoContent:
        return opFixedSuccess({
          outgoing_transactions: [],
          debit_account: undefined,
        });
      //FIXME: show more details in docs
      case HttpStatusCode.BadRequest:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      //FIXME: show more details in docs
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-bank-wire.html#post--admin-add-incoming
   *
   */
  async addIncoming(req: {
    body: TalerWireGatewayApi.AddIncomingRequest;
    auth: { username: string; password: string };
  }) {
    const url = new URL(`admin/add-incoming`, this.baseUrl);
    const resp = await this.httpLib.fetch(url.href, {
      method: "POST",
      headers: {
        Authorization: makeBasicAuthHeader(
          req.auth.username,
          req.auth.password,
        ),
      },
      body: req.body,
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForAddIncomingResponse());
      //FIXME: show more details in docs
      case HttpStatusCode.BadRequest:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      //FIXME: show more details in docs
      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-bank-wire.html#post--admin-add-kycauth
   *
   */
  async addKycAuth(req: {
    body: TalerWireGatewayApi.AddKycauthRequest;
    auth: { username: string; password: string };
  }) {
    const url = new URL(`admin/add-kycauth`, this.baseUrl);
    const resp = await this.httpLib.fetch(url.href, {
      method: "POST",
      headers: {
        Authorization: makeBasicAuthHeader(
          req.auth.username,
          req.auth.password,
        ),
      },
      body: req.body,
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForAddIncomingResponse());
      //FIXME: show more details in docs
      case HttpStatusCode.BadRequest:
        return opKnownHttpFailure(resp.status, resp);
      case HttpStatusCode.Unauthorized:
        return opKnownHttpFailure(resp.status, resp);
      //FIXME: show more details in docs
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }
}
