/*
 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 {
  AccountProperties,
  buildCodecForObject,
  Codec,
  codecForAccountProperties,
  codecForAny,
  codecForList,
  codecOptional,
  LegitimizationRuleSet
} from "./index.js";
import {
  Timestamp
} from "./types-taler-common.js";

// https://docs.taler.net/taler-kyc-manual.html#implementing-your-own-aml-programs

export type AmlProgram = (
  input: AmlProgramInput | undefined,
  config: string | undefined,
) => Promise<AmlOutcome>;

export type KycConverter = (
  input: object | undefined,
  config: string | undefined,
) => object;

export type AmlProgramParams = {
  name: string;
  debug?: boolean;
  showVersion?: boolean;
  showRequiredContext?: boolean;
  showRequiredAttributes?: boolean;
  showRequiredInputs?: boolean;
  showHelp?: boolean;
  config?: string;
};

export type KycConverterParams = {
  name: string;
  debug?: boolean;
  showVersion?: boolean;
  showOutputs?: boolean;
  showHelp?: boolean;
  config?: string;
};

export type KycConverterDefinition = {
  name: string;
  logic: KycConverter;
  outputs: string[];
};

export type AmlProgramDefinition = {
  name: string;
  requiredContext: string[];
  requiredAttributes: string[];
  requiredInputs: (keyof AmlProgramInput)[] | string[];
  logic: AmlProgram;
};

export interface AmlProgramInput {
  // JSON object that was provided as
  // part of the *measure*.  This JSON object is
  // provided under "context" in the main JSON object
  // input to the AML program.  This "context" should
  // satisfy both the REQUIRES clause of the respective
  // check and the output of "-r" from the
  // AML program's command-line option.
  context?: any;

  // JSON object that captures the
  // output of a [kyc-provider-] or (HTML) FORM.
  // In the case of KYC data provided by providers,
  // the keys in the JSON object will be the attribute
  // names and the values must be strings representing
  // the data. In the case of file uploads, the data
  // MUST be base64-encoded.
  // In the case of KYC data provided by HTML FORMs, the
  // keys will match the HTML FORM field names and
  // the values will use the KycStructuredFormData
  // encoding.
  attributes?: any;

  // JSON array with the results of historic
  // AML decisions about the account.
  aml_history?: AmlHistoryEntry[];

  // JSON array with the results of historic
  // KYC data about the account.
  kyc_history?: KycHistoryEntry[];

  // Default KYC rules of the exchange (exposed and not exposed).
  //
  // Default KYC rules are only provided if the AML program
  // specifies "default_rules" for its input requirements.
  default_rules?: LegitimizationRuleSet;

  // Current KYC rules the exchange applies for this user.
  // (exposed and not exposed).
  //
  // Current KYC rules are only provided if the AML program
  // specifies "current_rules" for its input requirements.
  current_rules?: LegitimizationRuleSet;
}

export interface AmlHistoryEntry {
  // When was the AML decision taken.
  decision_time: Timestamp;

  // What was the justification given for the decision.
  justification: string;

  // Public key of the AML officer taking the decision.
  decider_pub: string;

  // Properties associated with the account by the decision.
  properties: Object;

  // New set of legitimization rules that was put in place.
  new_rules: LegitimizationRuleSet;

  // True if the account was flagged for (further)
  // investigation.
  to_investigate: boolean;

  // True if this is the currently active decision.
  is_active: boolean;
}
export interface KycHistoryEntry {
  // Name of the provider
  // which was used to collect the attributes. NULL if they were
  // just uploaded via a form by the account owner.
  provider_name?: string;

  // True if the KYC process completed.
  finished: boolean;

  // Numeric error code, if the
  // KYC process did not succeed; 0 on success.
  code: number;

  // Human-readable description of code. Optional.
  hint?: string;

  // Optional detail given when the KYC process failed.
  error_message?: string;

  // Identifier of the user at the KYC provider. Optional.
  provider_user_id?: string;

  // Identifier of the KYC process at the KYC provider. Optional.
  provider_legitimization_id?: string;

  // The collected KYC data.
  // NULL if the attribute data could not
  // be decrypted or was not yet collected.
  attributes?: Object;

  // Time when the KYC data was collected
  collection_time: Timestamp;

  // Time when the KYC data will expire.
  expiration_time: Timestamp;
}

export interface AmlOutcome {
  // Should the client's account be investigated
  // by AML staff?
  // Defaults to false.
  to_investigate?: boolean;

  // Free-form properties about the account.
  // Can be used to store properties such as PEP,
  // risk category, type of business, hits on
  // sanctions lists, etc.
  properties?: AccountProperties;

  // Types of events to add to the KYC events table.
  // (for statistics).
  events?: string[];

  // KYC rules to apply.  Note that this
  // overrides *all* of the default rules
  // until the expiration_time and specifies
  // the successor measure to apply after the
  // expiration time.
  new_rules: LegitimizationRuleSet;

  // Space-separated list of measures to trigger
  // immediately on the account.
  // Prefixed with a "+" to indicate that the
  // measures should be ANDed.
  // Should typically be used to give the user some
  // information or request additional information.
  //
  // At most one measure with a SKIP check may be specified.
  new_measures?: string;
}


export const codecForAmlProgramInput = (): Codec<AmlProgramInput> =>
  buildCodecForObject<AmlProgramInput>()
    .property("aml_history", codecOptional(codecForList(codecForAny())))
    .property("kyc_history", codecOptional(codecForList(codecForAny())))
    .property("attributes", codecOptional(codecForAccountProperties()))
    .property("context", codecOptional(codecForAny()))
    .build("AmlProgramInput");
