/*
 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)
 * @author Nic Eigel
 */

/**
 * Imports.
 */
import {
  HttpResponse,
  HttpResponseOk,
  RequestError,
  RequestOptions,
  useApiContext,
} from "@gnu-taler/web-util/browser";
import { useCallback, useEffect, useState } from "preact/hooks";
import { useSWRConfig } from "swr";
import { useBackendContext } from "../context/backend.js";
import { AuditorBackend } from "../declaration.js";

export function useMatchMutate(): (
  re?: RegExp,
  value?: unknown,
) => Promise<any> {
  const { cache, mutate } = useSWRConfig();

  if (!(cache instanceof Map)) {
    throw new Error(
      "matchMutate requires the cache provider to be a Map instance",
    );
  }

  return function matchRegexMutate(re?: RegExp) {
    return mutate(
      (key) => {
        // evict if no key or regex === all
        if (!key || !re) return true;
        // match string
        if (typeof key === "string" && re.test(key)) return true;
        // record or object have the path at [0]
        if (typeof key === "object" && re.test(key[0])) return true;
        //key didn't match regex
        return false;
      },
      undefined,
      {
        revalidate: true,
      },
    );
  };
}

const CHECK_CONFIG_INTERVAL_OK = 5 * 60 * 1000;
const CHECK_CONFIG_INTERVAL_FAIL = 2 * 1000;

export function useBackendConfig(): HttpResponse<
  AuditorBackend.VersionResponse | undefined,
  RequestError<AuditorBackend.ErrorDetail>
> {
  const { request } = useBackendBaseRequest();

  type Type = AuditorBackend.VersionResponse;
  type State = {
    data: HttpResponse<Type, RequestError<AuditorBackend.ErrorDetail>>;
    timer: number;
  };
  const [result, setResult] = useState<State>({
    data: { loading: true },
    timer: 0,
  });

  useEffect(() => {
    if (result.timer) {
      clearTimeout(result.timer);
    }

    function tryConfig(): void {
      request<Type>("config")
        .then((data) => {
          const timer: any = setTimeout(() => {
            tryConfig();
          }, CHECK_CONFIG_INTERVAL_OK);
          setResult({ data, timer });
        })
        .catch((error) => {
          const timer: any = setTimeout(() => {
            tryConfig();
          }, CHECK_CONFIG_INTERVAL_FAIL);
          const data = error.cause;
          setResult({ data, timer });
        });
    }

    tryConfig();
  }, [request]);

  return result.data;
}

export function useBackendToken(): HttpResponse<
  AuditorBackend.VersionResponse,
  RequestError<AuditorBackend.ErrorDetail>
> {
  const { request } = useBackendBaseRequest();

  type Type = AuditorBackend.VersionResponse;
  type State = {
    data: HttpResponse<Type, RequestError<AuditorBackend.ErrorDetail>>;
    timer: number;
  };
  const [result, setResult] = useState<State>({
    data: { loading: true },
    timer: 0,
  });

  useEffect(() => {
    if (result.timer) {
      clearTimeout(result.timer);
    }

    function tryToken(): void {
      request<Type>(`monitoring/balances`)
        .then((data) => {
          const timer: any = setTimeout(() => {
            tryToken();
          }, CHECK_CONFIG_INTERVAL_OK);
          setResult({ data, timer });
        })
        .catch((error) => {
          const timer: any = setTimeout(() => {
            tryToken();
          }, CHECK_CONFIG_INTERVAL_FAIL);
          const data = error.cause;
          setResult({ data, timer });
        });
    }

    tryToken();
  }, [request]);

  return result.data;
}

interface useBackendInstanceRequestType {
  request: <T>(
    endpoint: string,
    options?: RequestOptions,
  ) => Promise<HttpResponseOk<T>>;
  fetcher: <T>(endpoint: string) => Promise<HttpResponseOk<T>>;
  multiFetcher: <T>(params: string[]) => Promise<HttpResponseOk<T>[]>;
}

interface useBackendBaseRequestType {
  request: <T>(
    endpoint: string,
    options?: RequestOptions,
  ) => Promise<HttpResponseOk<T>>;
}

/**
 *
 * @param root the request is intended to the base URL and no the instance URL
 * @returns request handler to
 */
//TODO: Add token
export function useBackendBaseRequest(): useBackendBaseRequestType {
  const { url: backend } = useBackendContext();
  const { request: requestHandler } = useApiContext();

  const request = useCallback(
    function requestImpl<T>(
      endpoint: string,
      //todo: remove
      options: RequestOptions = {},
    ): Promise<HttpResponseOk<T>> {
      return requestHandler<T>(backend, endpoint, { ...options })
        .then((res) => {
          return res;
        })
        .catch((err) => {
          throw err;
        });
    },
    [backend],
  );

  return { request };
}

export function useBackendRequest(): useBackendInstanceRequestType {
  const { url: baseUrl } = useBackendContext();
  const { request: requestHandler } = useApiContext();

  const request = useCallback(
    function requestImpl<T>(
      endpoint: string,
      options: RequestOptions = {},
    ): Promise<HttpResponseOk<T>> {
      return requestHandler<T>(baseUrl, endpoint, { ...options });
    },
    [baseUrl],
  );

  const multiFetcher = useCallback(
    function multiFetcherImpl<T>(
      params: string[],
      options: RequestOptions = {},
    ): Promise<HttpResponseOk<T>[]> {
      return Promise.all(
        params.map((endpoint) =>
          requestHandler<T>(baseUrl, endpoint, { ...options }),
        ),
      );
    },
    [baseUrl],
  );

  const fetcher = useCallback(
    function fetcherImpl<T>(endpoint: string): Promise<HttpResponseOk<T>> {
      return requestHandler<T>(baseUrl, endpoint, {});
    },
    [baseUrl],
  );

  return {
    request,
    fetcher,
    multiFetcher,
  };
}
