/*
 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 { canonicalizeBaseUrl } from "@gnu-taler/taler-util";
import {
  ErrorType,
  HttpError,
  RequestError,
  useLocalStorage,
} from "@gnu-taler/web-util/lib/index.browser";
import {
  HttpResponse,
  HttpResponseOk,
  RequestOptions,
} from "@gnu-taler/web-util/lib/index.browser";
import { useApiContext } from "@gnu-taler/web-util/lib/index.browser";
import { useCallback, useEffect, useState } from "preact/hooks";
import { useSWRConfig } from "swr";
import { useBackendContext } from "../context/backend.js";
import { bankUiSettings } from "../settings.js";
/**
 * Has the information to reach and
 * authenticate at the bank's backend.
 */
export type BackendState = LoggedIn | LoggedOut;
export interface BackendCredentials {
  username: string;
  password: string;
}
interface LoggedIn extends BackendCredentials {
  status: "loggedIn";
  isUserAdministrator: boolean;
}
interface LoggedOut {
  status: "loggedOut";
}
export function getInitialBackendBaseURL(): string {
  const overrideUrl =
    typeof localStorage !== "undefined"
      ? localStorage.getItem("bank-base-url")
      : undefined;
  if (!overrideUrl) {
    //normal path
    if (!bankUiSettings.backendBaseURL) {
      console.error(
        "ERROR: backendBaseURL was overridden by a setting file and missing. Setting value to 'window.origin'",
      );
      return canonicalizeBaseUrl(window.origin);
    }
    return canonicalizeBaseUrl(bankUiSettings.backendBaseURL);
  }
  // testing/development path
  return canonicalizeBaseUrl(overrideUrl);
}
export const defaultState: BackendState = {
  status: "loggedOut",
};
export interface BackendStateHandler {
  state: BackendState;
  logOut(): void;
  logIn(info: BackendCredentials): void;
}
/**
 * Return getters and setters for
 * login credentials and backend's
 * base URL.
 */
export function useBackendState(): BackendStateHandler {
  const { value, update } = useLocalStorage(
    "backend-state",
    JSON.stringify(defaultState),
  );
  let parsed;
  try {
    parsed = JSON.parse(value!);
  } catch {
    parsed = undefined;
  }
  const state: BackendState = !parsed?.status ? defaultState : parsed;
  return {
    state,
    logOut() {
      update(JSON.stringify({ ...defaultState }));
    },
    logIn(info) {
      //admin is defined by the username
      const nextState: BackendState = {
        status: "loggedIn",
        ...info,
        isUserAdministrator: info.username === "admin",
      };
      update(JSON.stringify(nextState));
    },
  };
}
interface useBackendType {
  request: (
    path: string,
    options?: RequestOptions,
  ) => Promise>;
  fetcher: (endpoint: string) => Promise>;
  multiFetcher: (endpoint: string[][]) => Promise[]>;
  paginatedFetcher: (
    args: [string, number, number],
  ) => Promise>;
  sandboxAccountsFetcher: (
    args: [string, number, number, string],
  ) => Promise>;
  sandboxCashoutFetcher: (endpoint: string[]) => Promise>;
}
export function usePublicBackend(): useBackendType {
  const { state } = useBackendContext();
  const { request: requestHandler } = useApiContext();
  const baseUrl = getInitialBackendBaseURL();
  const request = useCallback(
    function requestImpl(
      path: string,
      options: RequestOptions = {},
    ): Promise> {
      return requestHandler(baseUrl, path, options);
    },
    [baseUrl],
  );
  const fetcher = useCallback(
    function fetcherImpl(endpoint: string): Promise> {
      return requestHandler(baseUrl, endpoint);
    },
    [baseUrl],
  );
  const paginatedFetcher = useCallback(
    function fetcherImpl([endpoint, page, size]: [
      string,
      number,
      number,
    ]): Promise> {
      return requestHandler(baseUrl, endpoint, {
        params: { page: page || 1, size },
      });
    },
    [baseUrl],
  );
  const multiFetcher = useCallback(
    function multiFetcherImpl([endpoints]: string[][]): Promise<
      HttpResponseOk[]
    > {
      return Promise.all(
        endpoints.map((endpoint) => requestHandler(baseUrl, endpoint)),
      );
    },
    [baseUrl],
  );
  const sandboxAccountsFetcher = useCallback(
    function fetcherImpl([endpoint, page, size, account]: [
      string,
      number,
      number,
      string,
    ]): Promise> {
      return requestHandler(baseUrl, endpoint, {
        params: { page: page || 1, size },
      });
    },
    [baseUrl],
  );
  const sandboxCashoutFetcher = useCallback(
    function fetcherImpl([endpoint, account]: string[]): Promise<
      HttpResponseOk
    > {
      return requestHandler(baseUrl, endpoint);
    },
    [baseUrl],
  );
  return {
    request,
    fetcher,
    paginatedFetcher,
    multiFetcher,
    sandboxAccountsFetcher,
    sandboxCashoutFetcher,
  };
}
type CheckResult = ValidResult | RequestInvalidResult | InvalidationResult;
interface ValidResult {
  valid: true;
}
interface RequestInvalidResult {
  valid: false;
  requestError: true;
  cause: RequestError["cause"];
}
interface InvalidationResult {
  valid: false;
  requestError: false;
  error: unknown;
}
export function useCredentialsChecker() {
  const { request } = useApiContext();
  const baseUrl = getInitialBackendBaseURL();
  //check against account details endpoint
  //while sandbox backend doesn't have a login endpoint
  return async function testLogin(
    username: string,
    password: string,
  ): Promise {
    try {
      await request(baseUrl, `access-api/accounts/${username}/`, {
        basicAuth: { username, password },
        preventCache: true,
      });
      return { valid: true };
    } catch (error) {
      if (error instanceof RequestError) {
        return { valid: false, requestError: true, cause: error.cause };
      }
      return { valid: false, requestError: false, error };
    }
  };
}
export function useAuthenticatedBackend(): useBackendType {
  const { state } = useBackendContext();
  const { request: requestHandler } = useApiContext();
  const creds = state.status === "loggedIn" ? state : undefined;
  const baseUrl = getInitialBackendBaseURL();
  const request = useCallback(
    function requestImpl(
      path: string,
      options: RequestOptions = {},
    ): Promise> {
      return requestHandler(baseUrl, path, { basicAuth: creds, ...options });
    },
    [baseUrl, creds],
  );
  const fetcher = useCallback(
    function fetcherImpl(endpoint: string): Promise> {
      return requestHandler(baseUrl, endpoint, { basicAuth: creds });
    },
    [baseUrl, creds],
  );
  const paginatedFetcher = useCallback(
    function fetcherImpl([endpoint, page = 1, size]: [
      string,
      number,
      number,
    ]): Promise> {
      return requestHandler(baseUrl, endpoint, {
        basicAuth: creds,
        params: { page, size },
      });
    },
    [baseUrl, creds],
  );
  const multiFetcher = useCallback(
    function multiFetcherImpl([endpoints]: string[][]): Promise<
      HttpResponseOk[]
    > {
      return Promise.all(
        endpoints.map((endpoint) =>
          requestHandler(baseUrl, endpoint, { basicAuth: creds }),
        ),
      );
    },
    [baseUrl, creds],
  );
  const sandboxAccountsFetcher = useCallback(
    function fetcherImpl([endpoint, page, size, account]: [
      string,
      number,
      number,
      string,
    ]): Promise> {
      return requestHandler(baseUrl, endpoint, {
        basicAuth: creds,
        params: { page: page || 1, size },
      });
    },
    [baseUrl],
  );
  const sandboxCashoutFetcher = useCallback(
    function fetcherImpl([endpoint, account]: string[]): Promise<
      HttpResponseOk
    > {
      return requestHandler(baseUrl, endpoint, {
        basicAuth: creds,
        params: { account },
      });
    },
    [baseUrl, creds],
  );
  return {
    request,
    fetcher,
    paginatedFetcher,
    multiFetcher,
    sandboxAccountsFetcher,
    sandboxCashoutFetcher,
  };
}
/**
 *
 * @deprecated
 */
export function useBackendConfig(): HttpResponse<
  SandboxBackend.Config,
  SandboxBackend.SandboxError
> {
  const { request } = usePublicBackend();
  type Type = SandboxBackend.Config;
  const [result, setResult] = useState<
    HttpResponse
  >({ loading: true });
  useEffect(() => {
    request(`/config`)
      .then((data) => setResult(data))
      .catch((error) => setResult(error));
  }, [request]);
  return result;
}
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());
    const keys = allKeys.filter((key) => re.test(key));
    const mutations = keys.map((key) => {
      return mutate(key, value, true);
    });
    return Promise.all(mutations);
  };
}