/* eslint-disable consistent-return */
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable no-console */
/* eslint-disable camelcase */
import { MaxUint256 } from "@ethersproject/constants";
import { AddressZero } from "@ethersproject/constants/src.ts/addresses";
import { TransactionResponse } from "@ethersproject/providers";
import BigNumber from "bignumber.js";
import { useCallback, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { useAccount, usePublicClient, useWaitForTransaction, useWalletClient } from "wagmi";

import { Erc20__factory } from "../payTypes/Erc20__factory";
import { useERC20 } from "./useContract";
import useNotification from "./useNotification";
import useTokenAllowance from "./useTokenAllowance";

export enum ApprovalState {
  UNKNOWN,
  NOT_APPROVED,
  PENDING,
  APPROVED,
}
export function useApproveCallback(
  amount: BigNumber | null,
  tokenAddress: string,
  spender?: string
): [ApprovalState, () => Promise<TransactionResponse | undefined>, boolean, boolean] {
  const { address } = useAccount();
  const { t } = useTranslation();
  const [approveHash, setApproveHash] = useState("");
  const [approveLoading, setApproveLoading] = useState(false);
  const { failedTip, successTip } = useNotification();
  const { data: walletClient } = useWalletClient();
  const publicClient = usePublicClient();
  const { isLoading } = useWaitForTransaction({
    hash: approveHash as `0x${string}`,
    onSuccess() {
      if (approveHash) {
        successTip("Approve Success!");
        setApproveHash("");
        setApproveLoading(false);
      }
    },
    onError() {
      if (approveHash) {
        failedTip("Approve Failed!");
        setApproveHash("");
        setApproveLoading(false);
      }
    },
  });

  const currentAllowance = useTokenAllowance({ amount, address: tokenAddress }, address ?? undefined, spender, approveHash);
  // check the current approval status
  const approvalState: ApprovalState = useMemo(() => {
    if (!spender) return ApprovalState.UNKNOWN;
    if (tokenAddress === AddressZero) return ApprovalState.APPROVED;
    if (!currentAllowance) return ApprovalState.UNKNOWN;
    return currentAllowance?.isLessThan(amount || new BigNumber(0)) ? ApprovalState.NOT_APPROVED : ApprovalState.APPROVED;
  }, [amount, tokenAddress, currentAllowance, spender, address, approveHash]);

  const tokenContract = useERC20(tokenAddress);

  const approve = useCallback(async (): Promise<any> => {
    setApproveLoading(true);
    if (approvalState !== ApprovalState.NOT_APPROVED) {
      failedTip(t("Approve was called unnecessarily"));
      console.error("approve was called unnecessarily");
      setApproveLoading(false);
      return undefined;
    }
    if (!tokenAddress) {
      failedTip(t("No token"));
      console.error("no token");
      setApproveLoading(false);
      return undefined;
    }

    if (!tokenContract) {
      failedTip(t("Cannot find contract of the token %tokenAddress%", { tokenAddress }));
      console.error("tokenContract is null");
      setApproveLoading(false);
      return undefined;
    }

    if (!amount) {
      failedTip(t("Missing amount to approve"));
      console.error("missing amount to approve");
      setApproveLoading(false);
      return undefined;
    }

    if (!spender) {
      failedTip(t("No spender"));
      console.error("no spender");
      setApproveLoading(false);
      return undefined;
    }

    const useExact = false;

    try {
      const { request } = await publicClient.simulateContract({
        abi: Erc20__factory.abi,
        account: address as `0x${string}`,
        address: tokenContract.address as `0x${string}`,
        functionName: "approve",
        args: [spender, useExact ? amount.toString() : MaxUint256],
      });
      // @ts-ignore
      const hash = await walletClient?.writeContract(request);
      if (hash) {
        setApproveHash(hash);
        return hash;
      }
      setApproveLoading(false);
    } catch (error) {
      setApproveLoading(false);
    }
  }, [approvalState, tokenAddress, tokenContract, amount, spender, publicClient, walletClient]);

  return [approvalState, approve, approveLoading, isLoading];
}
