/*
 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 {
  HttpResponse,
  HttpResponseOk,
  HttpResponsePaginated,
  RequestError,
} from "@gnu-taler/web-util/browser";
import { useEffect, useState } from "preact/hooks";
import { useBackendContext } from "../context/backend.js";
import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils.js";
import {
  useAuthenticatedBackend,
  useMatchMutate,
  usePublicBackend,
} from "./backend.js";
// FIX default import https://github.com/microsoft/TypeScript/issues/49189
import _useSWR, { SWRHook } from "swr";
import { Amounts } from "@gnu-taler/taler-util";
const useSWR = _useSWR as unknown as SWRHook;
export function useAccessAPI(): AccessAPI {
  const mutateAll = useMatchMutate();
  const { request } = useAuthenticatedBackend();
  const { state } = useBackendContext();
  if (state.status === "loggedOut") {
    throw Error("access-api can't be used when the user is not logged In");
  }
  const account = state.username;
  const createWithdrawal = async (
    data: SandboxBackend.Access.BankAccountCreateWithdrawalRequest,
  ): Promise<
    HttpResponseOk
  > => {
    const res =
      await request(
        `access-api/accounts/${account}/withdrawals`,
        {
          method: "POST",
          data,
          contentType: "json",
        },
      );
    return res;
  };
  const createTransaction = async (
    data: SandboxBackend.Access.CreateBankAccountTransactionCreate,
  ): Promise> => {
    const res = await request(
      `access-api/accounts/${account}/transactions`,
      {
        method: "POST",
        data,
        contentType: "json",
      },
    );
    await mutateAll(/.*accounts\/.*\/transactions.*/);
    return res;
  };
  const deleteAccount = async (): Promise> => {
    const res = await request(`access-api/accounts/${account}`, {
      method: "DELETE",
      contentType: "json",
    });
    await mutateAll(/.*accounts\/.*/);
    return res;
  };
  return {
    createWithdrawal,
    createTransaction,
    deleteAccount,
  };
}
export function useAccessAnonAPI(): AccessAnonAPI {
  const mutateAll = useMatchMutate();
  const { request } = useAuthenticatedBackend();
  const abortWithdrawal = async (id: string): Promise> => {
    const res = await request(`access-api/withdrawals/${id}/abort`, {
      method: "POST",
      contentType: "json",
    });
    await mutateAll(/.*withdrawals\/.*/);
    return res;
  };
  const confirmWithdrawal = async (
    id: string,
  ): Promise> => {
    const res = await request(`access-api/withdrawals/${id}/confirm`, {
      method: "POST",
      contentType: "json",
    });
    await mutateAll(/.*withdrawals\/.*/);
    return res;
  };
  return {
    abortWithdrawal,
    confirmWithdrawal,
  };
}
export function useTestingAPI(): TestingAPI {
  const mutateAll = useMatchMutate();
  const { request: noAuthRequest } = usePublicBackend();
  const register = async (
    data: SandboxBackend.Access.BankRegistrationRequest,
  ): Promise> => {
    const res = await noAuthRequest(`access-api/testing/register`, {
      method: "POST",
      data,
      contentType: "json",
    });
    await mutateAll(/.*accounts\/.*/);
    return res;
  };
  return { register };
}
export interface TestingAPI {
  register: (
    data: SandboxBackend.Access.BankRegistrationRequest,
  ) => Promise>;
}
export interface AccessAPI {
  createWithdrawal: (
    data: SandboxBackend.Access.BankAccountCreateWithdrawalRequest,
  ) => Promise<
    HttpResponseOk
  >;
  createTransaction: (
    data: SandboxBackend.Access.CreateBankAccountTransactionCreate,
  ) => Promise>;
  deleteAccount: () => Promise>;
}
export interface AccessAnonAPI {
  abortWithdrawal: (wid: string) => Promise>;
  confirmWithdrawal: (wid: string) => Promise>;
}
export interface InstanceTemplateFilter {
  //FIXME: add filter to the template list
  position?: string;
}
export function useAccountDetails(
  account: string,
): HttpResponse<
  SandboxBackend.Access.BankAccountBalanceResponse,
  SandboxBackend.SandboxError
> {
  const { fetcher } = useAuthenticatedBackend();
  const { data, error } = useSWR<
    HttpResponseOk,
    RequestError
  >([`access-api/accounts/${account}`], fetcher, {
    refreshInterval: 0,
    refreshWhenHidden: false,
    revalidateOnFocus: false,
    revalidateOnReconnect: false,
    refreshWhenOffline: false,
    errorRetryCount: 0,
    errorRetryInterval: 1,
    shouldRetryOnError: false,
    keepPreviousData: true,
  });
  //FIXME: remove optional when libeufin sandbox has implemented the feature
  if (data && typeof data.data.debitThreshold === "undefined") {
    data.data.debitThreshold = "0";
  }
  //FIXME: sandbox server should return amount string
  if (data) {
    const isAmount = Amounts.parse(data.data.debitThreshold);
    if (isAmount) {
      //server response with correct format
      return data;
    }
    const { currency } = Amounts.parseOrThrow(data.data.balance.amount);
    const clone = structuredClone(data);
    const theNumber = Number.parseInt(data.data.debitThreshold, 10);
    const value = Number.isNaN(theNumber) ? 0 : theNumber;
    clone.data.debitThreshold = Amounts.stringify({
      currency,
      value: value,
      fraction: 0,
    });
    return clone;
  }
  if (error) return error.cause;
  return { loading: true };
}
// FIXME: should poll
export function useWithdrawalDetails(
  wid: string,
): HttpResponse<
  SandboxBackend.Access.BankAccountGetWithdrawalResponse,
  SandboxBackend.SandboxError
> {
  const { fetcher } = useAuthenticatedBackend();
  const { data, error } = useSWR<
    HttpResponseOk,
    RequestError
  >([`access-api/withdrawals/${wid}`], fetcher, {
    refreshInterval: 1000,
    refreshWhenHidden: false,
    revalidateOnFocus: false,
    revalidateOnReconnect: false,
    refreshWhenOffline: false,
    errorRetryCount: 0,
    errorRetryInterval: 1,
    shouldRetryOnError: false,
    keepPreviousData: true,
  });
  // if (isValidating) return { loading: true, data: data?.data };
  if (data) return data;
  if (error) return error.cause;
  return { loading: true };
}
export function useTransactionDetails(
  account: string,
  tid: string,
): HttpResponse<
  SandboxBackend.Access.BankAccountTransactionInfo,
  SandboxBackend.SandboxError
> {
  const { fetcher } = useAuthenticatedBackend();
  const { data, error } = useSWR<
    HttpResponseOk,
    RequestError
  >([`access-api/accounts/${account}/transactions/${tid}`], fetcher, {
    refreshInterval: 0,
    refreshWhenHidden: false,
    revalidateOnFocus: false,
    revalidateOnReconnect: false,
    refreshWhenOffline: false,
    errorRetryCount: 0,
    errorRetryInterval: 1,
    shouldRetryOnError: false,
    keepPreviousData: true,
  });
  // if (isValidating) return { loading: true, data: data?.data };
  if (data) return data;
  if (error) return error.cause;
  return { loading: true };
}
interface PaginationFilter {
  page: number;
}
export function usePublicAccounts(
  args?: PaginationFilter,
): HttpResponsePaginated<
  SandboxBackend.Access.PublicAccountsResponse,
  SandboxBackend.SandboxError
> {
  const { paginatedFetcher } = usePublicBackend();
  const [page, setPage] = useState(1);
  const {
    data: afterData,
    error: afterError,
    isValidating: loadingAfter,
  } = useSWR<
    HttpResponseOk,
    RequestError
  >([`access-api/public-accounts`, args?.page, PAGE_SIZE], paginatedFetcher);
  const [lastAfter, setLastAfter] = useState<
    HttpResponse<
      SandboxBackend.Access.PublicAccountsResponse,
      SandboxBackend.SandboxError
    >
  >({ loading: true });
  useEffect(() => {
    if (afterData) setLastAfter(afterData);
  }, [afterData]);
  if (afterError) return afterError.cause;
  // if the query returns less that we ask, then we have reach the end or beginning
  const isReachingEnd =
    afterData && afterData.data.publicAccounts.length < PAGE_SIZE;
  const isReachingStart = false;
  const pagination = {
    isReachingEnd,
    isReachingStart,
    loadMore: () => {
      if (!afterData || isReachingEnd) return;
      if (afterData.data.publicAccounts.length < MAX_RESULT_SIZE) {
        setPage(page + 1);
      }
    },
    loadMorePrev: () => {
      null;
    },
  };
  const publicAccounts = !afterData
    ? []
    : (afterData || lastAfter).data.publicAccounts;
  if (loadingAfter) return { loading: true, data: { publicAccounts } };
  if (afterData) {
    return { ok: true, data: { publicAccounts }, ...pagination };
  }
  return { loading: true };
}
/**
 * FIXME: mutate result when balance change (transaction )
 * @param account
 * @param args
 * @returns
 */
export function useTransactions(
  account: string,
  args?: PaginationFilter,
): HttpResponsePaginated<
  SandboxBackend.Access.BankAccountTransactionsResponse,
  SandboxBackend.SandboxError
> {
  const { paginatedFetcher } = useAuthenticatedBackend();
  const [page, setPage] = useState(1);
  const {
    data: afterData,
    error: afterError,
    isValidating: loadingAfter,
  } = useSWR<
    HttpResponseOk,
    RequestError
  >(
    [`access-api/accounts/${account}/transactions`, args?.page, PAGE_SIZE],
    paginatedFetcher,
  );
  const [lastAfter, setLastAfter] = useState<
    HttpResponse<
      SandboxBackend.Access.BankAccountTransactionsResponse,
      SandboxBackend.SandboxError
    >
  >({ loading: true });
  useEffect(() => {
    if (afterData) setLastAfter(afterData);
  }, [afterData]);
  if (afterError) {
    return afterError.cause;
  }
  // if the query returns less that we ask, then we have reach the end or beginning
  const isReachingEnd =
    afterData && afterData.data.transactions.length < PAGE_SIZE;
  const isReachingStart = false;
  const pagination = {
    isReachingEnd,
    isReachingStart,
    loadMore: () => {
      if (!afterData || isReachingEnd) return;
      if (afterData.data.transactions.length < MAX_RESULT_SIZE) {
        setPage(page + 1);
      }
    },
    loadMorePrev: () => {
      null;
    },
  };
  const transactions = !afterData
    ? []
    : (afterData || lastAfter).data.transactions;
  if (loadingAfter) return { loading: true, data: { transactions } };
  if (afterData) {
    return { ok: true, data: { transactions }, ...pagination };
  }
  return { loading: true };
}