/*
 This file is part of GNU Taler
 (C) 2021-2023 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/>
 */

/**
 *
 * @author Sebastian Javier Marchano (sebasjm)
 */

import { MockEnvironment } from "@gnu-taler/web-util/testing";
import { ComponentChildren, FunctionalComponent, h, VNode } from "preact";
import { HttpRequestLibrary, HttpRequestOptions, HttpResponse } from "@gnu-taler/taler-util/http";
import { SWRConfig } from "swr";
import { ApiContextProvider } from "@gnu-taler/web-util/browser";
import { BackendContextProvider } from "../context/backend.js";
import { InstanceContextProvider } from "../context/instance.js";
import { HttpResponseOk, RequestOptions } from "@gnu-taler/web-util/browser";
import { TalerBankIntegrationHttpClient, TalerCoreBankHttpClient, TalerRevenueHttpClient, TalerWireGatewayHttpClient } from "@gnu-taler/taler-util";

export class ApiMockEnvironment extends MockEnvironment {
  constructor(debug = false) {
    super(debug);
  }

  mockApiIfNeeded(): void {
    null; // do nothing
  }

  public buildTestingContext(): FunctionalComponent<{
    children: ComponentChildren;
  }> {
    const __SAVE_REQUEST_AND_GET_MOCKED_RESPONSE =
      this.saveRequestAndGetMockedResponse.bind(this);

    return function TestingContext({
      children,
    }: {
      children: ComponentChildren;
    }): VNode {

      async function request<T>(
        base: string,
        path: string,
        options: RequestOptions = {},
      ): Promise<HttpResponseOk<T>> {
        const _url = new URL(`${base}${path}`);
        // Object.entries(options.params ?? {}).forEach(([key, value]) => {
        //   _url.searchParams.set(key, String(value));
        // });

        const mocked = __SAVE_REQUEST_AND_GET_MOCKED_RESPONSE(
          {
            method: options.method ?? "GET",
            url: _url.href,
          },
          {
            qparam: options.params,
            auth: options.token,
            request: options.data,
          },
        );
        const status = mocked.expectedQuery?.query.code ?? 200;
        const requestPayload = mocked.expectedQuery?.params?.request;
        const responsePayload = mocked.expectedQuery?.params?.response;

        return {
          ok: true,
          data: responsePayload as T,
          loading: false,
          clientError: false,
          serverError: false,
          info: {
            hasToken: !!options.token,
            status,
            url: _url.href,
            payload: options.data,
            options: {},
          },
        };
      }
      const SC: any = SWRConfig;

      const mockHttpClient = new class implements HttpRequestLibrary {
        async fetch(url: string, options?: HttpRequestOptions | undefined): Promise<HttpResponse> {
          const _url = new URL(url);
          const mocked = __SAVE_REQUEST_AND_GET_MOCKED_RESPONSE(
            {
              method: options?.method ?? "GET",
              url: _url.href,
            },
            {
              qparam: _url.searchParams,
              auth: options as any,
              request: options?.body as any,
            },
          );
          const status = mocked.expectedQuery?.query.code ?? 200;
          const requestPayload = mocked.expectedQuery?.params?.request;
          const responsePayload = mocked.expectedQuery?.params?.response;

          // FIXME: complete this implementation to mock any query
          const resp: HttpResponse = {
            requestUrl: _url.href,
            status: status,
            headers: {} as any,
            requestMethod: options?.method ?? "GET",
            json: async () => responsePayload,
            text: async () => responsePayload as any as string,
            bytes: async () => responsePayload as ArrayBuffer,
          };
          return resp
        }
        get(url: string, opt?: HttpRequestOptions): Promise<HttpResponse> {
          return this.fetch(url, {
            method: "GET",
            ...opt,
          });
        }

        postJson(
          url: string,
          body: any,
          opt?: HttpRequestOptions,
        ): Promise<HttpResponse> {
          return this.fetch(url, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(body),
            ...opt,
          });
        }

      }
      const bankCore = new TalerCoreBankHttpClient("http://localhost", mockHttpClient)
      const bankIntegration = new TalerBankIntegrationHttpClient(bankCore.getIntegrationAPI().href, mockHttpClient)
      const bankRevenue = new TalerRevenueHttpClient(bankCore.getRevenueAPI("a").href, "a", mockHttpClient)
      const bankWire = new TalerWireGatewayHttpClient(bankCore.getWireGatewayAPI("b").href, "b", mockHttpClient)

      return (
        <BackendContextProvider defaultUrl="http://backend">
          <InstanceContextProvider
            value={{
              token: undefined,
              id: "default",
              admin: true,
              changeToken: () => null,
            }}
          >
            <ApiContextProvider value={{ request, bankCore, bankIntegration, bankRevenue, bankWire }}>
              <SC
                value={{
                  loadingTimeout: 0,
                  dedupingInterval: 0,
                  shouldRetryOnError: false,
                  errorRetryInterval: 0,
                  errorRetryCount: 0,
                  provider: () => new Map(),
                }}
              >
                {children}
              </SC>
            </ApiContextProvider>
          </InstanceContextProvider>
        </BackendContextProvider>
      );
    };
  }
}
