/*
 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 { TokenRequest } from "@gnu-taler/taler-util";
import {
  AbsoluteTime,
  Codec,
  TalerCorebankApi,
  buildCodecForObject,
  buildCodecForUnion,
  codecForAbsoluteTime,
  codecForAny,
  codecForConstString,
  codecForString,
  codecOptional,
} from "@gnu-taler/taler-util";
import {
  AppLocation,
  buildStorageKey,
  useLocalStorage,
} from "@gnu-taler/web-util/browser";


export type ChallengeInProgess =
  | LoginChallenge
  | DeleteAccountChallenge
  | UpdateAccountChallenge
  | UpdatePasswordChallenge
  | CreateTransactionChallenge
  | ConfirmWithdrawalChallenge
  | CashoutChallenge;

type BaseChallenge<OpType extends string, ReqType> = {
  id: string;
  operation: OpType;
  sent: AbsoluteTime;
  location: AppLocation | undefined;
  // info?: TalerCorebankApi.TanTransmission;
  request: ReqType;
};

type DeleteAccountChallenge = BaseChallenge<"delete-account", string>;
type LoginChallenge = BaseChallenge<
  "login",
  {
    tokenRequest: TokenRequest;
    username: string;
    password: string;
  }
>;
type UpdateAccountChallenge = BaseChallenge<
  "update-account",
  TalerCorebankApi.AccountReconfiguration
>;
type UpdatePasswordChallenge = BaseChallenge<
  "update-password",
  TalerCorebankApi.AccountPasswordChange
>;
type CreateTransactionChallenge = BaseChallenge<
  "create-transaction",
  TalerCorebankApi.CreateTransactionRequest
>;
type ConfirmWithdrawalChallenge = BaseChallenge<
  "confirm-withdrawal",
  TalerCorebankApi.BankAccountConfirmWithdrawalRequest & {
    id: string;
  }
>;
type CashoutChallenge = BaseChallenge<
  "create-cashout",
  TalerCorebankApi.CashoutRequest
>;

const codecForChallengeUpdatePassword = (): Codec<UpdatePasswordChallenge> =>
  buildCodecForObject<UpdatePasswordChallenge>()
    .property("operation", codecForConstString("update-password"))
    .property("id", codecForString())
    .property("location", codecForAppLocation())
    .property("sent", codecForAbsoluteTime)
    // .property("info", codecOptional(codecForTanTransmission()))
    .property("request", codecForAny())
    .build("UpdatePasswordChallenge");

const codecForChallengeDeleteAccount = (): Codec<DeleteAccountChallenge> =>
  buildCodecForObject<DeleteAccountChallenge>()
    .property("operation", codecForConstString("delete-account"))
    .property("id", codecForString())
    .property("location", codecForAppLocation())
    .property("sent", codecForAbsoluteTime)
    .property("request", codecForString())
    // .property("info", codecOptional(codecForTanTransmission()))
    .build("DeleteAccountChallenge");

const codecForChallengeUpdateAccount = (): Codec<UpdateAccountChallenge> =>
  buildCodecForObject<UpdateAccountChallenge>()
    .property("operation", codecForConstString("update-account"))
    .property("id", codecForString())
    .property("location", codecForAppLocation())
    .property("sent", codecForAbsoluteTime)
    // .property("info", codecOptional(codecForTanTransmission()))
    .property("request", codecForAny()) //FIXME: complete definition
    .build("UpdateAccountChallenge");

const codecForChallengeCreateTransaction =
  (): Codec<CreateTransactionChallenge> =>
    buildCodecForObject<CreateTransactionChallenge>()
      .property("operation", codecForConstString("create-transaction"))
      .property("id", codecForString())
      .property("location", codecForAppLocation())
      .property("sent", codecForAbsoluteTime)
      // .property("info", codecOptional(codecForTanTransmission()))
      .property("request", codecForAny()) //FIXME: complete definition
      .build("CreateTransactionChallenge");

const codecForChallengeConfirmWithdrawal =
  (): Codec<ConfirmWithdrawalChallenge> =>
    buildCodecForObject<ConfirmWithdrawalChallenge>()
      .property("operation", codecForConstString("confirm-withdrawal"))
      .property("id", codecForString())
      .property("location", codecForAppLocation())
      .property("sent", codecForAbsoluteTime)
      // .property("info", codecOptional(codecForTanTransmission()))
      .property("request", codecForAny()) //FIXME: complete definition
      .build("ConfirmWithdrawalChallenge");

const codecForAppLocation = codecForString as () => Codec<AppLocation>;

const codecForChallengeCashout = (): Codec<CashoutChallenge> =>
  buildCodecForObject<CashoutChallenge>()
    .property("operation", codecForConstString("create-cashout"))
    .property("id", codecForString())
    .property("location", codecForAppLocation())
    .property("sent", codecForAbsoluteTime)
    // .property("info", codecOptional(codecForTanTransmission()))
    .property("request", codecForAny()) //FIXME: complete definition
    .build("CashoutChallenge");

const codecForLoginChallenge = (): Codec<LoginChallenge> =>
  buildCodecForObject<LoginChallenge>()
    .property("operation", codecForConstString("login"))
    .property("id", codecForString())
    .property("location", codecOptional(codecForAppLocation()))
    .property("sent", codecForAbsoluteTime)
    // .property("info", codecOptional(codecForTanTransmission()))
    .property("request", codecForAny()) //FIXME: complete definition
    .build("LoginChallenge");

const codecForChallenge = (): Codec<ChallengeInProgess> =>
  buildCodecForUnion<ChallengeInProgess>()
    .discriminateOn("operation")
    .alternative("confirm-withdrawal", codecForChallengeConfirmWithdrawal())
    .alternative("create-cashout", codecForChallengeCashout())
    .alternative("create-transaction", codecForChallengeCreateTransaction())
    .alternative("delete-account", codecForChallengeDeleteAccount())
    .alternative("update-account", codecForChallengeUpdateAccount())
    .alternative("update-password", codecForChallengeUpdatePassword())
    .alternative("login", codecForLoginChallenge())
    .build("ChallengeInProgess");

interface BankState {
  currentWithdrawalOperationId: string | undefined;
  currentChallenge: ChallengeInProgess | undefined;
}

export const codecForBankState = (): Codec<BankState> =>
  buildCodecForObject<BankState>()
    .property("currentWithdrawalOperationId", codecOptional(codecForString()))
    .property("currentChallenge", codecOptional(codecForChallenge()))
    .build("BankState");

const defaultBankState: BankState = {
  currentWithdrawalOperationId: undefined,
  currentChallenge: undefined,
};

const BANK_STATE_KEY = buildStorageKey("bank-app-state", codecForBankState());

/**
 * Client state saved in local storage.
 *
 * This information is saved in the client because
 * the backend server session API is not enough.
 *
 * @returns tuple of [state, update(), reset()]
 */
export function useBankState(): [
  Readonly<BankState>,
  <T extends keyof BankState>(key: T, value: BankState[T]) => void,
  () => void,
] {
  const { value, update } = useLocalStorage(BANK_STATE_KEY, defaultBankState);

  function updateField<T extends keyof BankState>(k: T, v: BankState[T]) {
    const newValue = { ...value, [k]: v };
    update(newValue);
  }
  function reset() {
    update(defaultBankState);
  }
  return [value, updateField, reset];
}
