/*
 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 <http://www.gnu.org/licenses/>
 */

import {
  HttpResponse,
  HttpResponseOk,
  HttpResponsePaginated,
  RequestError,
} from "@gnu-taler/web-util/lib/index.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<SandboxBackend.Access.BankAccountCreateWithdrawalResponse>
  > => {
    const res =
      await request<SandboxBackend.Access.BankAccountCreateWithdrawalResponse>(
        `access-api/accounts/${account}/withdrawals`,
        {
          method: "POST",
          data,
          contentType: "json",
        },
      );
    return res;
  };
  const abortWithdrawal = async (id: string): Promise<HttpResponseOk<void>> => {
    const res = await request<void>(
      `access-api/accounts/${account}/withdrawals/${id}/abort`,
      {
        method: "POST",
        contentType: "json",
      },
    );
    await mutateAll(/.*accounts\/.*\/withdrawals\/.*/);
    return res;
  };
  const confirmWithdrawal = async (
    id: string,
  ): Promise<HttpResponseOk<void>> => {
    const res = await request<void>(
      `access-api/accounts/${account}/withdrawals/${id}/confirm`,
      {
        method: "POST",
        contentType: "json",
      },
    );
    await mutateAll(/.*accounts\/.*\/withdrawals\/.*/);
    return res;
  };
  const createTransaction = async (
    data: SandboxBackend.Access.CreateBankAccountTransactionCreate,
  ): Promise<HttpResponseOk<void>> => {
    const res = await request<void>(
      `access-api/accounts/${account}/transactions`,
      {
        method: "POST",
        data,
        contentType: "json",
      },
    );
    await mutateAll(/.*accounts\/.*\/transactions.*/);
    return res;
  };
  const deleteAccount = async (): Promise<HttpResponseOk<void>> => {
    const res = await request<void>(`access-api/accounts/${account}`, {
      method: "DELETE",
      contentType: "json",
    });
    await mutateAll(/.*accounts\/.*/);
    return res;
  };

  return {
    abortWithdrawal,
    confirmWithdrawal,
    createWithdrawal,
    createTransaction,
    deleteAccount,
  };
}

export function useTestingAPI(): TestingAPI {
  const mutateAll = useMatchMutate();
  const { request: noAuthRequest } = usePublicBackend();
  const register = async (
    data: SandboxBackend.Access.BankRegistrationRequest,
  ): Promise<HttpResponseOk<void>> => {
    const res = await noAuthRequest<void>(`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<HttpResponseOk<void>>;
}

export interface AccessAPI {
  createWithdrawal: (
    data: SandboxBackend.Access.BankAccountCreateWithdrawalRequest,
  ) => Promise<
    HttpResponseOk<SandboxBackend.Access.BankAccountCreateWithdrawalResponse>
  >;
  abortWithdrawal: (wid: string) => Promise<HttpResponseOk<void>>;
  confirmWithdrawal: (wid: string) => Promise<HttpResponseOk<void>>;
  createTransaction: (
    data: SandboxBackend.Access.CreateBankAccountTransactionCreate,
  ) => Promise<HttpResponseOk<void>>;
  deleteAccount: () => Promise<HttpResponseOk<void>>;
}

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<SandboxBackend.Access.BankAccountBalanceResponse>,
    RequestError<SandboxBackend.SandboxError>
  >([`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 = "100";
  }
  //FIXME: sandbox server should return amount string
  if (data) {
    const d = structuredClone(data);
    const { currency } = Amounts.parseOrThrow(data.data.balance.amount);
    d.data.debitThreshold = Amounts.stringify({
      currency,
      value: Number.parseInt(d.data.debitThreshold, 10),
      fraction: 0,
    });
    return d;
  }
  if (error) return error.info;
  return { loading: true };
}

// FIXME: should poll
export function useWithdrawalDetails(
  account: string,
  wid: string,
): HttpResponse<
  SandboxBackend.Access.BankAccountGetWithdrawalResponse,
  SandboxBackend.SandboxError
> {
  const { fetcher } = useAuthenticatedBackend();

  const { data, error } = useSWR<
    HttpResponseOk<SandboxBackend.Access.BankAccountGetWithdrawalResponse>,
    RequestError<SandboxBackend.SandboxError>
  >([`access-api/accounts/${account}/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.info;
  return { loading: true };
}

export function useTransactionDetails(
  account: string,
  tid: string,
): HttpResponse<
  SandboxBackend.Access.BankAccountTransactionInfo,
  SandboxBackend.SandboxError
> {
  const { fetcher } = useAuthenticatedBackend();

  const { data, error } = useSWR<
    HttpResponseOk<SandboxBackend.Access.BankAccountTransactionInfo>,
    RequestError<SandboxBackend.SandboxError>
  >([`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.info;
  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<SandboxBackend.Access.PublicAccountsResponse>,
    RequestError<SandboxBackend.SandboxError>
  >([`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.info;

  // 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<SandboxBackend.Access.BankAccountTransactionsResponse>,
    RequestError<SandboxBackend.SandboxError>
  >(
    [`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.info;

  // 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 };
}