import React, { FC, useState, useEffect, useCallback } from 'react';
import {
  Modal,
  ModalDrawer,
  Box,
  Text,
  Flex,
  Heading,
  IconCircled,
  icons,
  IconOutlined,
  Icon,
  ButtonLarge,
  LoadingDots,
  TransactionHash,
  Link,
  Hr,
  CollapsableCard,
  Button,
} from '@polymathnetwork/polymesh-ui';
import {
  PolymeshTransaction,
  TransactionQueue,
  TransactionStatus,
  TransactionQueueStatus,
  PolymeshError,
} from '@polymathnetwork/polymesh-sdk/types';
import { Polymesh } from '@polymathnetwork/polymesh-sdk';
import { PolymeshTransactionBase } from '@polymathnetwork/polymesh-sdk/internal';
import { useTranslation } from 'react-i18next';
import {
  SigningPackage,
  ConfirmationStatus,
  TransactionQueueResult,
  ApiQueueItem,
  ApiQueueItemType,
} from './definition';

// A store of data that was harvested from the previous tx events.
let previousTx: Record<string, any> = {};

export const TxQueueModal: FC<{
  sdk: Polymesh;
  store: any;
  signAndSend: any;
  transactionActions: {
    queueProcessedByMiddleware: any;
    transactionsCompleted: any;
    setConfirmationStatus: any;
    clearTransactions: any;
    [props: string]: any;
  };
  explorerUrl: string;
}> = ({ sdk, store, signAndSend, transactionActions, explorerUrl }) => {
  const { t } = useTranslation();
  const [transactions, setTransactions] = useState<PolymeshTransaction[]>([]);
  const [currentTransaction, setCurrentTransaction] = useState<number>(0);
  const [queue, setQueue] = useState<any[]>([]);
  const [isOpen, setIsOpen] = useState(false);
  const [canDismiss, setCanDismiss] = useState(false);
  const [isFinalized, setIsFinalized] = useState(false);
  const [hasErrors, setHasErrors] = useState(false);
  const [failure, setFailure] = useState(false);
  const [txHistory, setTxHistory] = useState<SigningPackage[]>([]);
  const { dispatch, getState } = store;
  const {
    transaction: { signingPackage },
    // isAsync,
  } = getState();
  const {
    id: signingPackageId,
    queue: txQueue,
    apiQueue,
    confirmationStatus,
    onCompleted,
    failUrl,
  } = (signingPackage as SigningPackage) || {};
  const isApi = !!apiQueue;

  const onTransactionStatusChange = (
    tx: PolymeshTransactionBase<unknown[], unknown[]>,
    txQueueObj: TransactionQueue
  ) => {
    setTransactions([...(txQueueObj.transactions as PolymeshTransaction[])]);
  };

  const onProcessedByMiddleware = useCallback(
    (error?: PolymeshError) => {
      dispatch(
        transactionActions.queueProcessedByMiddleware({
          queue: txQueue,
          error,
        })
      );
    },
    [txQueue, dispatch]
  );

  const onQueueStatusChange = (newTxQueue: TransactionQueue) => {
    if (
      newTxQueue.status === TransactionQueueStatus.Succeeded ||
      newTxQueue.status === TransactionQueueStatus.Failed
    ) {
      setIsFinalized(true);
      setFailure(newTxQueue.status === TransactionQueueStatus.Failed);
    } else {
      setIsFinalized(false);
    }
  };

  const runQueueSdk = useCallback(async () => {
    if (!txQueue) return;
    try {
      setTransactions([...(txQueue.transactions as PolymeshTransaction[])]);
      txQueue.onTransactionStatusChange(onTransactionStatusChange);
      txQueue.onStatusChange(onQueueStatusChange);
      txQueue.onProcessedByMiddleware(onProcessedByMiddleware);
      await txQueue.run();
    } catch (e) {} // eslint-disable-line no-empty
  }, [txQueue, onProcessedByMiddleware]);

  const runQueueApi = useCallback(async () => {
    if (!apiQueue) return;
    setTransactions(apiQueue as any);
    setQueue(apiQueue);
    setCurrentTransaction(0);
    setIsFinalized(false);
    setHasErrors(false);
    setFailure(false);
  }, [apiQueue]);

  useEffect(() => {
    const runQueue = async () => {
      if (confirmationStatus === ConfirmationStatus.Done) {
        if (isApi) {
          await runQueueApi();
        } else {
          await runQueueSdk();
        }
      }
    };
    runQueue();
  }, [txQueue, apiQueue, confirmationStatus, isApi, runQueueApi, runQueueSdk]);

  // Process next transaction if using API
  useEffect(() => {
    if (isApi && queue.length > 0 && sdk) {
      const processTransaction = async (tx: ApiQueueItem<any>) => {
        try {
          const tempQueue = [...queue];
          // Set the transaction status to minting
          tempQueue.splice(currentTransaction, 1, {
            ...queue[currentTransaction],
            status: TransactionStatus.Running,
          });
          setQueue([...tempQueue]);
          // Submit the transaction
          if (tx.type === ApiQueueItemType.Batch) {
            const batchParams = tx.params.map((param: any) => {
              if (
                param.method !== undefined &&
                typeof param.method === 'function' &&
                param.params !== undefined
              ) {
                const methodParams = param.params.map((p: any) => {
                  if (typeof p === 'function') {
                    return p(previousTx);
                  }
                  return p;
                });
                return param.method(...methodParams);
              }
              return null;
            });
            tx.params = [batchParams];
          } else {
            tx.params = tx.params.map((param: any) => {
              // this param is actually a function that reads the value of a param
              // from previous tx store.
              if (typeof param === 'function') {
                return param(previousTx);
              }
              return param;
            });
          }

          const [status, isError, data] = await signAndSend(
            sdk,
            tx.method,
            tx.params,
            tx.dataResolver
          );
          previousTx = { ...previousTx, ...data };

          // Update the queue
          tempQueue.splice(currentTransaction, 1, {
            ...queue[currentTransaction],
            status: isError
              ? TransactionStatus.Failed
              : TransactionStatus.Succeeded,
            txHash: status.hash.toString(),
          });

          // Determine what to do next
          if (isError) {
            // The transaction failed
            if (tx.blocking) {
              // The failed transaction is a show stopper
              setFailure(true);
            } else {
              // non show-stopper
              setHasErrors(true);
            }
          }

          if (transactions) {
            setTransactions(
              transactions.map((transaction, index): any => {
                if (index === currentTransaction) {
                  return {
                    ...transaction,
                    status: isError
                      ? TransactionStatus.Failed
                      : TransactionStatus.Succeeded,
                  };
                }
                return transaction;
              })
            );
          }

          setCurrentTransaction(currentTransaction + 1);
          setQueue([...tempQueue]);
        } catch (e) {
          if (e.message === 'Cancelled') {
            setFailure(true);
            const tempQueue = [...queue];
            tempQueue.splice(currentTransaction, 1, {
              ...queue[currentTransaction],
              status: TransactionStatus.Rejected,
            });
            setQueue([...tempQueue]);
          } else if (e.message === '1010: Invalid Transaction: Payment') {
            setFailure(true);
            const tempQueue = [...queue];
            tempQueue.splice(currentTransaction, 1, {
              ...queue[currentTransaction],
              status: TransactionStatus.Failed,
            });
            setQueue([...tempQueue]);
          } else {
            setFailure(true);
            const tempQueue = [...queue];
            tempQueue.splice(currentTransaction, 1, {
              ...queue[currentTransaction],
              status: TransactionStatus.Failed,
            });
            setQueue([...tempQueue]);
          }
        }
      };

      // Look if there is a transaction in progress
      const inProgress = queue.find((item) => {
        return item.status === TransactionStatus.Running;
      });

      if (inProgress) {
        // There is already a transaction in progress
        return;
      }

      if (queue.length === 0) {
        // Empty queue
      } else if (currentTransaction >= queue.length || failure) {
        // All done
        setIsFinalized(true);
      } else {
        // Get the next item from the queue
        const tx = queue[currentTransaction];
        // Process the transaction
        processTransaction(tx);
      }
    }
  }, [isApi, queue, confirmationStatus, currentTransaction, sdk]); // eslint-disable-line

  const getQueueStatus = () =>
    transactions.filter((tq) => tq.status === TransactionStatus.Running).length
      ? TransactionStatus.Running
      : (transactions || queue).filter(
          (tq) =>
            tq.status === TransactionStatus.Failed ||
            tq.status === TransactionStatus.Rejected ||
            tq.status === TransactionStatus.Aborted
        ).length
      ? TransactionStatus.Failed
      : (transactions || queue).filter(
          (tq) => tq.status === TransactionStatus.Succeeded
        ).length
      ? TransactionStatus.Succeeded
      : TransactionStatus.Idle;

  const renderStatusIcon = (status: TransactionStatus) => {
    const fail = (
      <IconOutlined
        Asset={icons.SvgClose}
        width={24}
        height={24}
        scale={0.75}
        color="alert"
      />
    );
    const success = (
      <IconOutlined
        Asset={icons.SvgCheckmark}
        width={24}
        height={24}
        scale={0.75}
        color="success"
      />
    );
    if (isApi && (hasErrors || failure)) return fail;
    if (isApi && isFinalized && !hasErrors) return success;
    switch (status) {
      case TransactionStatus.Idle:
        return (
          <Icon
            Asset={icons.SvgClock}
            width={24}
            height={24}
            color="placeholder"
          />
        );
      case TransactionStatus.Unapproved:
        return (
          <Icon
            Asset={icons.SvgClock}
            width={24}
            height={24}
            color="brandMain"
          />
        );
      case TransactionStatus.Running:
        return (
          <Icon
            Asset={icons.SvgClock}
            width={24}
            height={24}
            color="brandMain"
          />
        );
      case TransactionStatus.Failed:
        return fail;
      case TransactionStatus.Succeeded:
        return success;
    }
  };

  const getText = ({
    text,
    tag,
    status,
  }: {
    text: string;
    tag: string;
    status: TransactionStatus;
  }) => {
    const fail = (
      <>
        {t(`${tag}.${text}.${TransactionStatus.Failed}`)}
        {failUrl && text === 'statusText' && (
          <>
            {' '}
            <Link href={failUrl}>{t('General.learnMore')}</Link>
          </>
        )}
      </>
    );
    if (isApi && (hasErrors || failure)) return fail;

    const success = t(`${tag}.${text}.${TransactionStatus.Succeeded}`);
    if (isApi && isFinalized && !hasErrors) return success;

    return t(`${tag}.${text}.${status}`);
  };

  const renderTransactions = (txs: any[]) =>
    txs?.map((tx, index) => {
      return (
        <Box key={index} mb="m">
          <Box mb="s">
            <Text variant="b2m" color="gray.2">
              {getText({
                text: 'title',
                tag: tx.tag,
                status: tx.status || TransactionStatus.Idle,
              })}
            </Text>
          </Box>
          {tx.txHash && (
            <TransactionHash
              hash={tx.txHash}
              link={`${explorerUrl}/transaction/${tx.txHash}`}
              copyLink
              size={55}
              flexProps={{
                flexDirection: 'row',
                justifyContent: 'flex-start',
                mb: 'm',
              }}
              buttonProps={{
                copyId: 'queue_modal_txhash_copy_button',
                linkId: 'queue_modal_txhash_link_button',
              }}
            />
          )}
          <Box
            borderRadius="3"
            borderColor="gray.4"
            border="1px solid"
            borderWidth="1px"
            mb="4"
            p="m"
            width={456}
          >
            <Flex flexDirection={1}>
              <Box>{renderStatusIcon(tx.status || TransactionStatus.Idle)}</Box>
              <Box mx="s">
                <Text variant="b2m" color="gray.2">
                  {getText({
                    text: 'statusText',
                    tag: tx.tag,
                    status: tx.status || TransactionStatus.Idle,
                  })}
                </Text>
              </Box>
            </Flex>
          </Box>
        </Box>
      );
    });

  useEffect(() => {
    const doneTxs = transactions.filter((tx) => !!tx.txHash);
    if (doneTxs.length) {
      setCanDismiss(true);
    } else {
      setCanDismiss(false);
    }
  }, [transactions]);

  useEffect(() => {
    if (confirmationStatus === ConfirmationStatus.Done) {
      setIsOpen(true);
    }
  }, [confirmationStatus]);

  const onDone = () => {
    let result: TransactionQueueResult;
    if (!failure) {
      result = TransactionQueueResult.Success;
      if (hasErrors) {
        result = TransactionQueueResult.Partial;
      } else {
        result = TransactionQueueResult.Success;
      }
    } else {
      result = TransactionQueueResult.Failure;
      // if (isAsync) dispatch(asyncActions.setAsync(false));
    }

    setTxHistory([...(signingPackage ? [signingPackage] : []), ...txHistory]);

    dispatch(
      transactionActions.transactionsCompleted({
        result,
        queue: txQueue,
        apiQueue,
        id: signingPackageId,
      })
    );

    dispatch(transactionActions.setConfirmationStatus(ConfirmationStatus.None));

    if (onCompleted) {
      onCompleted(result, isApi ? apiQueue : txQueue?.transactions);
    }

    // const txTag = isApi ? queueType : txQueue?.transactions?.[0]?.tag;
    // heap.click({ id: `${txTag}_queue_modal_done_button` });
  };

  useEffect(() => {
    if (isFinalized) {
      setIsOpen(false);
      onDone();
    }
  }, [isFinalized]); // eslint-disable-line

  const renderHeader = () => (
    <>
      <Box>
        {!isFinalized && <LoadingDots />}
        {isFinalized && (
          <IconCircled
            Asset={failure ? icons.SvgClose : icons.SvgCheckmark}
            width={48}
            height={48}
            scale={0.75}
            bg={!isFinalized ? 'brandLightest' : failure ? 'red.1' : 'green.2'}
            color={!isFinalized ? 'brandMain' : failure ? 'alert' : 'success'}
          />
        )}
      </Box>
      <Box ml="m">
        <Heading variant="h5">
          {getText({
            text: 'heading',
            tag: signingPackageId,
            status: getQueueStatus(),
          })}
        </Heading>
      </Box>
    </>
  );

  // const onModalDestroyed = () => {
  //   setIsFinalized(false);
  //   setHasErrors(false);
  //   setFailure(false);
  //   setQueue([]);
  //   dispatch(transactionActions.clearTransactions());
  //   // Callback
  //   if (onCompleted) {
  //     let result: TransactionQueueResult;
  //     if (!failure) {
  //       result = TransactionQueueResult.Success;
  //       if (hasErrors) {
  //         result = TransactionQueueResult.Partial;
  //       } else {
  //         result = TransactionQueueResult.Success;
  //       }
  //     } else {
  //       result = TransactionQueueResult.Failure;
  //       if (isAsync) dispatch(asyncActions.setAsync(false));
  //     }
  //     onCompleted(result, isApi ? apiQueue : txQueue?.transactions);
  //   }
  // };

  const toggleDrawer = () => {
    setIsOpen(!isOpen);
  };

  const dismiss = () => {
    setIsOpen(false);
    // if (onCompleted) {
    //   onCompleted(
    //     TransactionQueueResult.Success,
    //     isApi ? apiQueue : txQueue?.transactions
    //   );
    // }
  };

  const isOngoing = !isFinalized && (txQueue || apiQueue);

  return (
    <>
      <Button
        style={{
          position: 'fixed',
          top: 3,
          left: -10,
          padding: '0 0 0 10px',
          width: 50,
          minWidth: 'unset',
          zIndex: 9999,
        }}
        onClick={toggleDrawer}
      >
        <Icon Asset={icons.SvgFormatListBulleted} width={22} height={22} />
      </Button>
      <ModalDrawer isCloseable={false} isOpen={isOpen}>
        <Modal.Body width={500} height="100%">
          {!isOngoing && !txHistory.length && (
            <Box mt="xl">
              <Text variant="b1m" color="gray.1">
                No transactions
              </Text>
            </Box>
          )}
          {isOngoing && (
            <>
              <Flex my="l">{renderHeader()}</Flex>
              {renderTransactions(transactions)}
              <Flex justifyContent="flex-end" mt="l" mb="xl">
                {canDismiss && (
                  <ButtonLarge onClick={dismiss}>
                    Finalize in background
                  </ButtonLarge>
                )}
              </Flex>
            </>
          )}
          {isOngoing && !!txHistory.length && <Hr />}
          {!!txHistory.length && (
            <CollapsableCard
              variant="transparent"
              title="Earlier Transactions"
              mt="xl"
              defaultOpen
            >
              {txHistory.map((h) => (
                <Box key={h.id} mb="m">
                  {renderTransactions(
                    h.queue?.transactions || h.apiQueue || []
                  )}
                </Box>
              ))}
            </CollapsableCard>
          )}
        </Modal.Body>
      </ModalDrawer>
    </>
  );
};
