/*
 This file is part of GNU Taler
 (C) 2024-2025 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 { codecForAmountString } from "./amounts.js";
import {
  buildCodecForObject,
  buildCodecForUnion,
  Codec,
  codecForAny,
  codecForConstString,
  codecForList,
  codecForNumber,
  codecForString,
} from "./codec.js";
import { codecForTimestamp, TalerProtocolTimestamp } from "./time.js";
import {
  EddsaPublicKeyString,
  BlindedRsaSignature,
  codecForEddsaPublicKey,
  codecForEddsaSignature,
  Cs25519Point,
  Cs25519Scalar,
  CSNonce,
  CsRPublic,
  EddsaPublicKey,
  EddsaSignatureString,
  HashCodeString,
  Integer,
  RsaPublicKeyString,
  RsaSignature,
  AmountString,
} from "./types-taler-common.js";
import { DenomKeyType } from "./types-taler-exchange.js";

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

  // Name of the protocol.
  name: "donau";

  // Currency supported by this Donau.
  currency: string;

  // Financial domain by this Donau.
  legal_domain: string;
}

export const codecForDonauVersionResponse = (): Codec<DonauVersionResponse> =>
  buildCodecForObject<DonauVersionResponse>()
    .property("version", codecForString())
    .property("name", codecForConstString("donau"))
    .property("currency", codecForString())
    .property("legal_domain", codecForString())
    .build("DonauApi.DonauVersionResponse");

/**
 * Structure of one exchange signing key in the /keys response.
 */
export class DonauSignKeyJson {
  stamp_start: TalerProtocolTimestamp;
  stamp_expire: TalerProtocolTimestamp;
  stamp_end: TalerProtocolTimestamp;
  key: EddsaPublicKeyString;
  master_sig: EddsaSignatureString;
}

export const codecForDonauSignKeyJson = (): Codec<DonauSignKeyJson> =>
  buildCodecForObject<DonauSignKeyJson>()
    .property("key", codecForEddsaPublicKey())
    .property("master_sig", codecForEddsaSignature())
    .property("stamp_end", codecForTimestamp)
    .property("stamp_start", codecForTimestamp)
    .property("stamp_expire", codecForTimestamp)
    .build("DonauSignKeyJson");

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

  // Financial domain this Donau operates for.
  //domain: string;

  // The Donau's base URL.
  base_url: string;

  // The Donau's currency.
  currency: string;

  // How many digits should the amounts be rendered
  // with by default. Small capitals should
  // be used to render fractions beyond the number
  // given here (like on gas stations).
  //currency_fraction_digits: number;

  // Donation Units offered by this Donau
  donation_units: DonationUnitKeyGroup[];

  // The Donau's signing keys.
  signkeys: DonauSignKeyJson[];
}

export type DonationUnitKeyGroup =
  | DonationUnitKeyGroupRsa
  | DonationUnitKeyGroupCs;

export interface DonationUnitKeyGroupCommon {
  // How much are coins of this denomination worth?
  value: AmountString;

  // For which year is this donation unit key valid.
  year: number;

  // Set to 'true' if the Donau somehow "lost" the private key. The donation unit was not
  // revoked, but still cannot be used to withdraw receipts at this time (theoretically,
  // the private key could be recovered in the future; receipts signed with the private key
  // remain valid).
  lost?: boolean;
}

export interface DonationUnitKeyGroupRsa extends DonationUnitKeyGroupCommon {
  donation_unit_pub: {
    cipher: "RSA";
    pub_key_hash: HashCodeString;
    rsa_public_key: RsaPublicKeyString;
  };
}

export interface DonationUnitKeyGroupCs extends DonationUnitKeyGroupCommon {
  donation_unit_pub: {
    cipher: "CS";
    pub_key_hash: HashCodeString;
    cs_pub: Cs25519Point;
  };
}

// FIXME: Validate properly!
export const codecForDonationUnitKeyGroup: Codec<DonationUnitKeyGroup> =
  codecForAny();

export const codecForDonauKeysResponse = (): Codec<DonauKeysResponse> =>
  buildCodecForObject<DonauKeysResponse>()
    .property("version", codecForString())
    .property("base_url", codecForString())
    .property("currency", codecForString())
    //.property("domain", codecForString())
    .property("signkeys", codecForAny())
    .property("donation_units", codecForList(codecForDonationUnitKeyGroup))
    //.property("currency_fraction_digits", codecForNumber())
    .build("DonauApi.DonauKeysResponse");

export interface BlindedDonationReceiptKeyPair {
  // Hash of the public key that should be used to sign
  // the donation receipt.
  h_donation_unit_pub: HashCodeString;

  // Blinded value to give to the Donau to sign over.
  blinded_udi: BlindedUniqueDonationIdentifier;
}

export type BlindedUniqueDonationIdentifier = RSABUDI | CSBUDI;

export interface RSABUDI {
  cipher: "RSA";
  rsa_blinded_identifier: string; // Crockford Base32 encoded
}

// For donation unit signatures based on Blind Clause-Schnorr, the BUDI
// consists of the public nonce and two Curve25519 scalars which are two
// blinded challenges in the Blinded Clause-Schnorr signature scheme.
// See https://taler.net/papers/cs-thesis.pdf for details.
export interface CSBUDI {
  cipher: "CS";
  cs_nonce: string; // Crockford Base32 encoded
  cs_blinded_c0: string; // Crockford Base32 encoded
  cs_blinded_c1: string; // Crockford Base32 encoded
}

export type DonationReceiptSignature =
  | RSADonationReceiptSignature
  | CSDonationReceiptSignature;

export interface RSADonationReceiptSignature {
  cipher: "RSA";

  // RSA signature
  rsa_signature: RsaSignature;
}

export interface CSDonationReceiptSignature {
  cipher: "CS";

  // R value component of the signature.
  cs_signature_r: Cs25519Point;

  // s value component of the signature.
  cs_signature_s: Cs25519Scalar;
}

export type DonauUnitPubKey = RsaDonauUnitPubKey | CsDonauUnitPubKey;

export interface RsaDonauUnitPubKey {
  readonly cipher: DenomKeyType.Rsa;
  readonly rsa_public_key: string;
  readonly age_mask: number;
}

export interface CsDonauUnitPubKey {
  readonly cipher: DenomKeyType.ClauseSchnorr;
  readonly age_mask: number;
  readonly cs_public_key: string;
}

export interface CharityRequest {
  // Long-term EdDSA public key that identifies the charity.
  charity_pub: EddsaPublicKey;
  // Canonical URL that should be presented to donors.
  charity_url: string;
  // Human-readable display name of the charity.
  charity_name: string;
  // Allowed donation volume for the charity per calendar year.
  max_per_year: AmountString;
  // Donation volume that has already been received for current_year.
  receipts_to_date: AmountString;
  // Calendar year the accounting information refers to.
  current_year: Integer;
}

export interface IssuePrepareRequest {
  // Nonce to be used by the donau to derive
  // its private inputs from. Must not have ever
  // been used before.
  nonce: CSNonce;

  // Hash of the public key of the donation unit
  // the request relates to.
  du_pub_hash: HashCodeString;
}
export interface DonauCharityResponse {
  charity_id: Integer;
}

export interface IssueReceiptsRequest {
  // Signature by the charity approving that the
  // Donau should sign the donation receipts below.
  charity_sig: EddsaSignatureString;

  // Year for which the donation receipts are expected.
  // Also determines which keys are used to sign the
  // blinded donation receipts.
  year: Integer;

  // Array of blinded donation receipts to sign.
  // Must NOT be empty (if no donation receipts
  // are desired, just leave the entire donau
  // argument blank).
  budikeypairs: BlindedDonationReceiptKeyPair[];
}

export type IssuePrepareResponse = DonauIssueValue;

export type DonauIssueValue = DonauRsaIssueValue | DonauCsIssueValue;

export interface DonauRsaIssueValue {
  cipher: "RSA";
}

export interface DonauCsIssueValue {
  cipher: "CS";

  // CSR R0 value
  r_pub_0: CsRPublic;

  // CSR R1 value
  r_pub_1: CsRPublic;
}

export interface BlindedDonationReceiptSignatures {
  blind_signed_receipt_signatures: BlindedDonationReceiptSignature[];
}
export type BlindedDonationReceiptSignature =
  | RSABlindedDonationReceiptSignature
  | CSBlindedDonationReceiptSignature;
export interface RSABlindedDonationReceiptSignature {
  cipher: "RSA";

  // (blinded) RSA signature
  blinded_rsa_signature: BlindedRsaSignature;
}
export interface CSBlindedDonationReceiptSignature {
  cipher: "CS";

  // Signer chosen bit value, 0 or 1, used
  // in Clause Blind Schnorr to make the
  // ROS problem harder.
  b: Integer;

  // Blinded scalar calculated from c_b.
  s: Cs25519Scalar;
}

export const codecForIssuePrepareResponse = (): Codec<IssuePrepareResponse> =>
  buildCodecForUnion<IssuePrepareResponse>()
    .discriminateOn("cipher")
    .alternative(
      "CS",
      codecForAny(), //FIXME: complete
    )
    .alternative(
      "RSA",
      codecForAny(), //FIXME: complete
    )
    .build<IssuePrepareResponse>("DonauApi.IssuePrepareResponse");

export const codecForDonauCharityResponse = (): Codec<DonauCharityResponse> =>
  buildCodecForObject<DonauCharityResponse>()
    .property("charity_id", codecForNumber())
    .build("DonauApi.DonauCharityResponse");

export interface SubmitDonationReceiptsRequest {
  // hashed taxpayer ID plus salt
  h_donor_tax_id: HashCodeString;
  // All donation receipts must be for this year.
  donation_year: Integer;
  // Receipts should be sorted by amount.
  donation_receipts: DonationReceipt[];
}

export interface DonationReceipt {
  h_donation_unit_pub: HashCodeString;
  nonce: string;
  donation_unit_sig: DonationReceiptSignature;
}

export interface DonationStatementResponse {
  total: AmountString;
  // signature over h_donor_tax_id, total, donation_year
  donation_statement_sig: EddsaSignatureString;
  // the corresponding public key to the signature
  donau_pub: EddsaPublicKeyString;
}

export const codecForDonauDonationStatementResponse =
  (): Codec<DonationStatementResponse> =>
    buildCodecForObject<DonationStatementResponse>()
      .property("total", codecForAmountString())
      .property("donau_pub", codecForEddsaPublicKey())
      .property("donation_statement_sig", codecForEddsaSignature())
      .build("DonauApi.DonationStatementResponse");

export interface Charities {
  charities: CharitySummary[];
}

export interface CharitySummary {
  charity_id: Integer;
  charity_pub: EddsaPublicKeyString;
  name: string;
  max_per_year: AmountString;
  receipts_to_date: AmountString;
}

export interface Charity {
  charity_pub: EddsaPublicKey;
  name: string;
  url: string;
  max_per_year: AmountString;
  receipts_to_date: AmountString;
  current_year: Integer;
}
