/*
 This file is part of GNU Taler
 (C) 2022 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 
 */
import { NotificationType } from "@gnu-taler/taler-util";
import {
  WalletCoreApiClient,
  WalletCoreOpKeys,
  WalletCoreRequestType,
  WalletCoreResponseType,
} from "@gnu-taler/taler-wallet-core";
import { TranslationProvider } from "@gnu-taler/web-util/browser";
import {
  ComponentChildren,
  FunctionalComponent,
  VNode,
  h as create,
} from "preact";
import { AlertProvider } from "./context/alert.js";
import { BackendProvider } from "./context/backend.js";
import { strings } from "./i18n/strings.js";
import { nullFunction } from "./mui/handlers.js";
import { BackgroundApiClient, wxApi } from "./wxApi.js";
// export const nullFunction: any = () => null;
interface MockHandler {
  addWalletCallResponse(
    operation: Op,
    payload?: Partial>,
    response?: WalletCoreResponseType,
    callback?: () => void,
  ): MockHandler;
  getCallingQueueState(): "empty" | string;
  notifyEventFromWallet(event: NotificationType): void;
}
type CallRecord = WalletCallRecord | BackgroundCallRecord;
interface WalletCallRecord {
  source: "wallet";
  callback: () => void;
  operation: WalletCoreOpKeys;
  payload?: WalletCoreRequestType;
  response?: WalletCoreResponseType;
}
interface BackgroundCallRecord {
  source: "background";
  name: string;
  args: any;
  response: any;
}
type Subscriptions = {
  [key in NotificationType]?: VoidFunction;
};
export function createWalletApiMock(): {
  handler: MockHandler;
  TestingContext: FunctionalComponent<{ children: ComponentChildren }>;
} {
  const calls = new Array();
  const subscriptions: Subscriptions = {};
  const mock: typeof wxApi = {
    wallet: new Proxy({} as any, {
      get(target, name, receiver) {
        const functionName = String(name);
        if (functionName !== "call") {
          throw Error(
            `the only method in wallet api should be 'call': ${functionName}`,
          );
        }
        return function (
          operation: WalletCoreOpKeys,
          payload: WalletCoreRequestType,
        ) {
          const next = calls.shift();
          if (!next) {
            throw Error(
              `wallet operation was called but none was expected: ${operation} (${JSON.stringify(
                payload,
                undefined,
                2,
              )})`,
            );
          }
          if (next.source !== "wallet") {
            throw Error(`wallet operation expected`);
          }
          if (operation !== next.operation) {
            //more checks, deep check payload
            throw Error(
              `wallet operation doesn't match: expected ${next.operation} actual ${operation}`,
            );
          }
          next.callback();
          return next.response ?? {};
        };
      },
    }),
    listener: {
      onUpdateNotification(
        mTypes: NotificationType[],
        callback: (() => void) | undefined,
      ): () => void {
        mTypes.forEach((m) => {
          subscriptions[m] = callback;
        });
        return nullFunction;
      },
    },
    background: new Proxy({} as any, {
      get(target, name, receiver) {
        const functionName = String(name);
        return function (...args: any) {
          const next = calls.shift();
          if (!next) {
            throw Error(
              `background operation was called but none was expected: ${functionName} (${JSON.stringify(
                args,
                undefined,
                2,
              )})`,
            );
          }
          if (next.source !== "background" || functionName !== next.name) {
            //more checks, deep check args
            throw Error(`background operation doesn't match`);
          }
          return next.response;
        };
      },
    }),
  };
  const handler: MockHandler = {
    addWalletCallResponse(operation, payload, response, cb) {
      calls.push({
        source: "wallet",
        operation,
        payload,
        response,
        callback: cb
          ? cb
          : () => {
              null;
            },
      });
      return handler;
    },
    notifyEventFromWallet(event: NotificationType): void {
      const callback = subscriptions[event];
      if (!callback)
        throw Error(`Expected to have a subscription for ${event}`);
      return callback();
    },
    getCallingQueueState() {
      return calls.length === 0 ? "empty" : `${calls.length} left`;
    },
  };
  function TestingContext({
    children: _cs,
  }: {
    children: ComponentChildren;
  }): VNode {
    let children = _cs;
    children = create(AlertProvider, { children }, children);
    children = create(
      TranslationProvider,
      { children, source: strings, initial: "en", forceLang: "en" },
      children,
    );
    return create(
      BackendProvider,
      {
        wallet: mock.wallet,
        background: mock.background,
        listener: mock.listener,
        children,
      },
      children,
    );
  }
  return { handler, TestingContext };
}