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

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

import {
  TalerBankIntegrationHttpClient,
  TalerCoreBankHttpClient,
  TalerRevenueHttpClient,
  TalerWireGatewayHttpClient,
} from "@gnu-taler/taler-util";
import {
  HttpRequestLibrary,
  HttpRequestOptions,
  HttpResponse,
} from "@gnu-taler/taler-util/http";
import { ApiContextProvider } from "@gnu-taler/web-util/browser";
import { MockEnvironment } from "@gnu-taler/web-util/testing";
import { ComponentChildren, FunctionalComponent, h, VNode } from "preact";
import { SWRConfig } from "swr";

interface RequestOptions {
  method?: "GET" | "POST" | "HEAD";
  params?: any;
  token?: string | undefined;
  data?: any;
}
interface HttpResponseOk<T> {
  ok: true;
  data: T;
  loading: boolean;
  clientError: boolean;
  serverError: boolean;
  info: any;
}

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,
        mockHttpClient,
      );
      const bankWire = new TalerWireGatewayHttpClient(
        bankCore.getWireGatewayAPI("b").href,
        {
          httpClient: mockHttpClient,
        },
      );

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