/*
 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 
 */
/**
 *
 * @author Sebastian Javier Marchano (sebasjm)
 */
import { useSWRConfig } from "swr";
import axios, { AxiosError, AxiosResponse } from "axios";
import { MerchantBackend } from "../declaration.js";
import { useBackendContext } from "../context/backend.js";
import { useEffect, useState } from "preact/hooks";
import { DEFAULT_REQUEST_TIMEOUT } from "../utils/constants.js";
import {
  axiosHandler,
  removeAxiosCancelToken,
} from "../utils/switchableAxios.js";
export function useMatchMutate(): (
  re: RegExp,
  value?: unknown,
) => Promise {
  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, value?: unknown) {
    const allKeys = Array.from(cache.keys());
    // console.log(allKeys)
    const keys = allKeys.filter((key) => re.test(key));
    // console.log(allKeys.length, keys.length)
    const mutations = keys.map((key) => {
      // console.log(key)
      mutate(key, value, true);
    });
    return Promise.all(mutations);
  };
}
export type HttpResponse =
  | HttpResponseOk
  | HttpResponseLoading
  | HttpError;
export type HttpResponsePaginated =
  | HttpResponseOkPaginated
  | HttpResponseLoading
  | HttpError;
export interface RequestInfo {
  url: string;
  hasToken: boolean;
  params: unknown;
  data: unknown;
  status: number;
}
interface HttpResponseLoading {
  ok?: false;
  loading: true;
  clientError?: false;
  serverError?: false;
  data?: T;
}
export interface HttpResponseOk {
  ok: true;
  loading?: false;
  clientError?: false;
  serverError?: false;
  data: T;
  info?: RequestInfo;
}
export type HttpResponseOkPaginated = HttpResponseOk & WithPagination;
export interface WithPagination {
  loadMore: () => void;
  loadMorePrev: () => void;
  isReachingEnd?: boolean;
  isReachingStart?: boolean;
}
export type HttpError =
  | HttpResponseClientError
  | HttpResponseServerError
  | HttpResponseUnexpectedError;
export interface SwrError {
  info: unknown;
  status: number;
  message: string;
}
export interface HttpResponseServerError {
  ok?: false;
  loading?: false;
  clientError?: false;
  serverError: true;
  error?: MerchantBackend.ErrorDetail;
  status: number;
  message: string;
  info?: RequestInfo;
}
interface HttpResponseClientError {
  ok?: false;
  loading?: false;
  clientError: true;
  serverError?: false;
  info?: RequestInfo;
  isUnauthorized: boolean;
  isNotfound: boolean;
  status: number;
  error?: MerchantBackend.ErrorDetail;
  message: string;
}
interface HttpResponseUnexpectedError {
  ok?: false;
  loading?: false;
  clientError?: false;
  serverError?: false;
  info?: RequestInfo;
  status?: number;
  error: unknown;
  message: string;
}
type Methods = "get" | "post" | "patch" | "delete" | "put";
interface RequestOptions {
  method?: Methods;
  token?: string;
  data?: unknown;
  params?: unknown;
}
function buildRequestOk(
  res: AxiosResponse,
  url: string,
  hasToken: boolean,
): HttpResponseOk {
  return {
    ok: true,
    data: res.data,
    info: {
      params: res.config.params,
      data: res.config.data,
      url,
      hasToken,
      status: res.status,
    },
  };
}
// function buildResponse(data?: T, error?: MerchantBackend.ErrorDetail, isValidating?: boolean): HttpResponse {
//   if (isValidating) return {loading: true}
//   if (error) return buildRequestFailed()
// }
function buildRequestFailed(
  ex: AxiosError,
  url: string,
  hasToken: boolean,
):
  | HttpResponseClientError
  | HttpResponseServerError
  | HttpResponseUnexpectedError {
  const status = ex.response?.status;
  const info: RequestInfo = {
    data: ex.request?.data,
    params: ex.request?.params,
    url,
    hasToken,
    status: status || 0,
  };
  if (status && status >= 400 && status < 500) {
    const error: HttpResponseClientError = {
      clientError: true,
      isNotfound: status === 404,
      isUnauthorized: status === 401,
      status,
      info,
      message: ex.response?.data?.hint || ex.message,
      error: ex.response?.data,
    };
    return error;
  }
  if (status && status >= 500 && status < 600) {
    const error: HttpResponseServerError = {
      serverError: true,
      status,
      info,
      message:
        `${ex.response?.data?.hint} (code ${ex.response?.data?.code})` ||
        ex.message,
      error: ex.response?.data,
    };
    return error;
  }
  const error: HttpResponseUnexpectedError = {
    info,
    status,
    error: ex,
    message: ex.message,
  };
  return error;
}
const CancelToken = axios.CancelToken;
let source = CancelToken.source();
export function cancelPendingRequest(): void {
  source.cancel("canceled by the user");
  source = CancelToken.source();
}
export function isAxiosError(
  error: AxiosError | any,
): error is AxiosError {
  return error && error.isAxiosError;
}
export async function request(
  url: string,
  options: RequestOptions = {},
): Promise> {
  const headers = options.token
    ? { Authorization: `Bearer ${options.token}` }
    : undefined;
  try {
    const res = await axiosHandler({
      url,
      responseType: "json",
      headers,
      cancelToken: !removeAxiosCancelToken ? source.token : undefined,
      method: options.method || "get",
      data: options.data,
      params: options.params,
      timeout: DEFAULT_REQUEST_TIMEOUT * 1000,
    });
    return buildRequestOk(res, url, !!options.token);
  } catch (e) {
    if (isAxiosError(e)) {
      const error = buildRequestFailed(e, url, !!options.token);
      throw error;
    }
    throw e;
  }
}
export function multiFetcher(
  urls: string[],
  token: string,
  backend: string,
): Promise[]> {
  return Promise.all(urls.map((url) => fetcher(url, token, backend)));
}
export function fetcher(
  url: string,
  token: string,
  backend: string,
): Promise> {
  return request(`${backend}${url}`, { token });
}
export function useBackendInstancesTestForAdmin(): HttpResponse {
  const { url, token } = useBackendContext();
  type Type = MerchantBackend.Instances.InstancesResponse;
  const [result, setResult] = useState>({ loading: true });
  useEffect(() => {
    request(`${url}/management/instances`, { token })
      .then((data) => setResult(data))
      .catch((error) => setResult(error));
  }, [url, token]);
  return result;
}
export function useBackendConfig(): HttpResponse {
  const { url, token } = useBackendContext();
  type Type = MerchantBackend.VersionResponse;
  const [result, setResult] = useState>({ loading: true });
  useEffect(() => {
    request(`${url}/config`, { token })
      .then((data) => setResult(data))
      .catch((error) => setResult(error));
  }, [url, token]);
  return result;
}