/*
 This file is part of GNU Taler
 (C) 2022-2025 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 } from "../http-common.js";
import { HttpStatusCode } from "../http-status-codes.js";
import { createPlatformHttpLib } from "../http.js";
import { LibtoolVersion } from "../libtool-version.js";
import {
  carefullyParseConfig,
  opEmptySuccess,
  OperationFail,
  OperationOk,
  opFixedSuccess,
  opKnownFailure,
  opKnownHttpFailure,
  opSuccessFromHttp,
  opUnknownHttpFailure,
} from "../operation.js";

import { AccessToken, Codec, codecForAny } from "../index.js";
import {
  BlindedDonationReceiptSignatures,
  Charities,
  Charity,
  CharityRequest,
  codecForDonauCharityResponse,
  codecForDonauDonationStatementResponse,
  codecForDonauKeysResponse,
  codecForDonauVersionResponse,
  codecForIssuePrepareResponse,
  DonauVersionResponse,
  IssuePrepareRequest,
  IssueReceiptsRequest,
  SubmitDonationReceiptsRequest,
} from "../types-donau.js";
import { makeBearerTokenAuthHeader } from "./utils.js";

/**
 * Client library for the GNU Taler donau service.
 */
export class DonauHttpClient {
  public static readonly SUPPORTED_DONAU_PROTOCOL_VERSION = "0:0:0";
  private httpLib: HttpRequestLibrary;

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

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

  /**
   * https://docs.taler.net/core/api-donau.html#get--keys
   *
   * @returns
   */
  async getKeys() {
    const url = new URL(`keys`, this.baseUrl);
    const resp = await this.httpLib.fetch(url.href, {
      method: "GET",
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForDonauKeysResponse());
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-donau.html#get--seed
   *
   */
  async getSeed() {
    const url = new URL(`keys`, this.baseUrl);
    const resp = await this.httpLib.fetch(url.href, {
      method: "GET",
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        const buffer = await resp.bytes();
        const uintar = new Uint8Array(buffer);
        return opFixedSuccess(uintar);
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-donau.html#get--config
   *
   * @returns
   */
  async getConfig(): Promise<
    OperationOk<DonauVersionResponse> | OperationFail<HttpStatusCode.NotFound>
  > {
    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(
          "donau",
          DonauHttpClient.SUPPORTED_DONAU_PROTOCOL_VERSION,
          resp,
          codecForDonauVersionResponse(),
        );
      case HttpStatusCode.NotFound:
        return opKnownHttpFailure(resp.status, resp);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-donau.html#csr-issue
   *
   * @param args
   * @returns
   */
  async prepareIssueReceipt(body: IssuePrepareRequest) {
    const url = new URL(`csr-issue`, this.baseUrl);
    const resp = await this.httpLib.fetch(url.href, {
      method: "POST",
      body,
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForIssuePrepareResponse());
      case HttpStatusCode.NotFound:
        return opKnownFailure(resp.status);
      case HttpStatusCode.Gone:
        return opKnownFailure(resp.status);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-donau.html#post--batch-issue-$CHARITY_ID
   *
   * @param args
   * @returns
   */
  async issueReceipts(charityId: number, body: IssueReceiptsRequest) {
    const url = new URL(`batch-issue/${charityId}`, this.baseUrl);
    const resp = await this.httpLib.fetch(url.href, {
      method: "POST",
      body,
    });
    //FIXME: incomplete
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(
          resp,
          codecForAny() as Codec<BlindedDonationReceiptSignatures>,
        );
      case HttpStatusCode.Forbidden:
        return opKnownFailure(resp.status);
      case HttpStatusCode.NotFound:
        return opKnownFailure(resp.status);
      case HttpStatusCode.Conflict:
        return opKnownFailure(resp.status);
      case HttpStatusCode.Gone:
        return opKnownFailure(resp.status);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-donau.html#post--batch-submit
   *
   * @param args
   * @returns
   */
  async submitDonationReceipts(body: SubmitDonationReceiptsRequest) {
    const url = new URL(`batch-submit`, this.baseUrl);
    const resp = await this.httpLib.fetch(url.href, {
      method: "POST",
      body,
    });
    switch (resp.status) {
      case HttpStatusCode.Created:
        return opEmptySuccess();
      case HttpStatusCode.Forbidden:
        return opKnownFailure(resp.status);
      case HttpStatusCode.NotFound:
        return opKnownFailure(resp.status);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-donau.html#get--donation-statement-$YEAR-$HASH_DONOR_ID
   *
   * @param args
   * @returns
   */
  async getDonationStatement(year: number, hash: string) {
    const url = new URL(`donation-statement/${year}/${hash}`, this.baseUrl);
    const resp = await this.httpLib.fetch(url.href, {
      method: "GET",
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(
          resp,
          codecForDonauDonationStatementResponse(),
        );
      case HttpStatusCode.Forbidden:
        return opKnownFailure(resp.status);
      case HttpStatusCode.NotFound:
        return opKnownFailure(resp.status);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-donau.html#get--charities
   *
   * @param args
   * @returns
   */
  async getCharities(token: AccessToken): Promise<OperationOk<Charities>> {
    const url = new URL(`charities`, this.baseUrl);
    const resp = await this.httpLib.fetch(url.href, {
      method: "GET",
      headers: {
        Authorization: makeBearerTokenAuthHeader(token),
      },
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForAny() as Codec<Charities>); // FIXME: complete codec
      case HttpStatusCode.NoContent:
        return opFixedSuccess({
          charities: [],
        });
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-donau.html#get--charities-$CHARITY_ID
   */
  async getCharitiesById(
    token: AccessToken,
    id: string,
  ): Promise<OperationFail<HttpStatusCode.NotFound> | OperationOk<Charity>> {
    const url = new URL(`charities/${id}`, this.baseUrl);
    const resp = await this.httpLib.fetch(url.href, {
      method: "GET",
      headers: {
        Authorization: makeBearerTokenAuthHeader(token),
      },
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opSuccessFromHttp(resp, codecForAny() as Codec<Charity>); // FIXME: complete codec
      case HttpStatusCode.NotFound:
        return opKnownFailure(resp.status);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-donau.html#post--charities
   *
   * @param args
   * @returns
   */
  async createCharity(token: AccessToken, body: CharityRequest) {
    const url = new URL(`charities`, this.baseUrl);
    const resp = await this.httpLib.fetch(url.href, {
      method: "POST",
      body,
      headers: {
        Authorization: makeBearerTokenAuthHeader(token),
      },
    });
    switch (resp.status) {
      case HttpStatusCode.Created:
        return opSuccessFromHttp(resp, codecForDonauCharityResponse());
      case HttpStatusCode.NoContent:
        return opKnownFailure(resp.status);
      case HttpStatusCode.Forbidden:
        return opKnownFailure(resp.status);
      case HttpStatusCode.NotFound:
        return opKnownFailure(resp.status);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-donau.html#patch--charities-id
   *
   * @returns
   */
  async updateCharity(token: AccessToken, id: number, body: CharityRequest) {
    const url = new URL(`charities/${id}`, this.baseUrl);
    const resp = await this.httpLib.fetch(url.href, {
      method: "PATCH",
      body,
      headers: {
        Authorization: makeBearerTokenAuthHeader(token),
      },
    });
    switch (resp.status) {
      case HttpStatusCode.Ok:
        return opEmptySuccess();
      case HttpStatusCode.Forbidden:
        return opKnownFailure(resp.status);
      case HttpStatusCode.NotFound: // FIXME: missing in the spec
        return opKnownFailure(resp.status);
      default:
        return opUnknownHttpFailure(resp);
    }
  }

  /**
   * https://docs.taler.net/core/api-donau.html#patch--charities-id
   *
   * @returns
   */
  async deleteCharity(token: AccessToken, id: number) {
    const url = new URL(`charities/${id}`, this.baseUrl);
    const resp = await this.httpLib.fetch(url.href, {
      method: "DELETE",
      headers: {
        Authorization: makeBearerTokenAuthHeader(token),
      },
    });
    switch (resp.status) {
      case HttpStatusCode.NoContent:
        return opEmptySuccess();
      case HttpStatusCode.Forbidden:
        return opKnownFailure(resp.status);
      case HttpStatusCode.NotFound: // FIXME: missing in the spec
        return opKnownFailure(resp.status);
      default:
        return opUnknownHttpFailure(resp);
    }
  }
}
