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 {
  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,
  TxQueueStatus,
  ApiQueueItem,
  ApiQueueItemType,
} from './definition';
import * as heap from '../analytics/heap';

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

const getTxTag = (tx: any) =>
  tx.tag || `${tx.method.section}.${tx.method.method}`;

const getQueueStatus = (queue: any) => {
  const status: {
    total: number;
    state: { [key: string]: any };
    overall: TxQueueStatus;
  } = {
    total: queue.length,
    state: {},
    overall: TxQueueStatus.None,
  };
  queue.forEach((q: any) => {
    if (status.state[q.status]) status.state[q.status] += 1;
    else status.state[q.status] = 1;
  });
  let overall = TxQueueStatus.None;
  if (
    (status.state[TransactionStatus.Idle] &&
      status.state[TransactionStatus.Idle] > 0) ||
    (status.state[TransactionStatus.Unapproved] &&
      status.state[TransactionStatus.Unapproved] > 0) ||
    (status.state[TransactionStatus.Running] &&
      status.state[TransactionStatus.Running] > 0)
  ) {
    overall = TxQueueStatus.Running;
  } else if (
    status.state[TransactionStatus.Succeeded] &&
    status.state[TransactionStatus.Succeeded] === status.total
  ) {
    overall = TxQueueStatus.Succeeded;
  } else if (
    status.state[TransactionStatus.Failed] &&
    status.state[TransactionStatus.Failed] === status.total
  ) {
    overall = TxQueueStatus.Failed;
  } else if (
    status.state[TransactionStatus.Failed] &&
    status.state[TransactionStatus.Failed] > 0 &&
    status.state[TransactionStatus.Succeeded] &&
    status.state[TransactionStatus.Succeeded] > 0
  ) {
    overall = TxQueueStatus.Partial;
  }
  status.overall = overall;
  return status;
};

export const TxQueueModal: FC<{
  sdk: Polymesh;
  store: any;
  signAndSend: any;
  transactionActions: {
    queueProcessedByMiddleware: any;
    transactionsCompleted: any;
    setConfirmationStatus: any;
    clearTransactions: any;
    [props: string]: any;
  };
  appStateActions: {
    setInFlight: any;
    [key: string]: any;
  };
  explorerUrl: string;
}> = ({
  sdk,
  store,
  signAndSend,
  transactionActions,
  appStateActions,
  explorerUrl,
}) => {
  const { t } = useTranslation();
  const [transactions, setTransactions] = useState<
    PolymeshTransactionBase<unknown[], unknown[]>[] | ApiQueueItem<any>[]
  >([]);
  const [currentTransaction, setCurrentTransaction] = useState<number>(0);
  const [isOpen, setIsOpen] = useState(false);
  const [canDismiss, setCanDismiss] = useState(false);
  const [isFinalized, setIsFinalized] = useState(false);
  const [txHistory, setTxHistory] = useState<SigningPackage[]>([]);
  const { dispatch, getState } = store;
  const {
    transaction: { signingPackage },
    polymesh: { inFlight },
  } = getState();
  const {
    id: signingPackageId,
    queue,
    apiQueue,
    confirmationStatus,
    onCompleted,
    onDismissed,
    failUrl,
  } = (signingPackage as SigningPackage) || {};
  const isApi = !!apiQueue;
  const queueStatus = getQueueStatus(transactions);
  const isOngoing = queueStatus.overall === TxQueueStatus.Running;
  const hasFailed =
    queueStatus.overall === TxQueueStatus.Failed ||
    queueStatus.overall === TxQueueStatus.Partial;

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

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

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

  // Kick-off on confirmation
  useEffect(() => {
    const runQueue = async () => {
      setTransactions(apiQueue || queue?.transactions || []);
      if (queue && !isApi) {
        try {
          queue.onTransactionStatusChange(onTransactionStatusChange);
          queue.onStatusChange(onQueueStatusChange);
          queue.onProcessedByMiddleware(onProcessedByMiddleware);
          await queue.run();
        } catch (e) {} // eslint-disable-line no-empty
      }
    };
    if (confirmationStatus === ConfirmationStatus.Done) {
      runQueue();
      setIsOpen(true);
    }
  }, [queue, apiQueue, confirmationStatus, isApi]); // eslint-disable-line

  // Process transactions if API queue
  useEffect(() => {
    if (isApi && transactions.length > 0 && sdk) {
      const setTxState = (state: Partial<ApiQueueItem<any>>) => {
        const tempQueue = [...transactions] as ApiQueueItem<any>[];
        tempQueue.splice(currentTransaction, 1, {
          ...transactions[currentTransaction],
          ...state,
        } as any);
        setTransactions([...tempQueue]);
      };

      const processTransaction = async (tx: ApiQueueItem<any>) => {
        try {
          setTxState({ status: TransactionStatus.Running });

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

          setTxState({
            status: isError
              ? TransactionStatus.Failed
              : TransactionStatus.Succeeded,
            txHash: status.hash.toString(),
          });

          if (isError) {
            if (tx.blocking) {
              setIsFinalized(true);
            }
            // else {
            //   // non show-stopper
            // }
          }

          setCurrentTransaction(currentTransaction + 1);
        } catch (e) {
          if (e.message === 'Cancelled') {
            setTxState({ status: TransactionStatus.Rejected });
          } else {
            setTxState({ status: TransactionStatus.Failed });
          }
        }
      };

      const inProgress = (transactions as ApiQueueItem<any>[]).find((item) => {
        return item.status === TransactionStatus.Running;
      });
      if (inProgress) return;

      if (currentTransaction >= transactions.length || hasFailed) {
        setIsFinalized(true);
      } else {
        const tx = transactions[currentTransaction] as ApiQueueItem<any>;
        processTransaction(tx);
      }
    }
  }, [
    sdk,
    isApi,
    hasFailed,
    transactions,
    currentTransaction,
    confirmationStatus,
  ]);

  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 && hasFailed) return fail;
    // if (isApi && isFinalized && !hasFailed) 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 | TxQueueStatus;
  }) => {
    const value = t(`${tag}.${text}.${status}`);
    const fail = (
      <>
        {value}
        {failUrl && text === 'statusText' && (
          <>
            {' '}
            <Link href={failUrl}>{t('General.learnMore')}</Link>
          </>
        )}
      </>
    );
    if (status === TransactionStatus.Failed) return fail;
    return value;
  };

  const renderTransactions = (txs: any[], renderStatus = true) =>
    txs?.map((tx, index) => {
      const txTag = getTxTag(tx);
      return (
        <Box key={index} mb="m">
          <Box mb="s">
            <Text variant="b2m" color="gray.2">
              {getText({
                text: 'title',
                tag: txTag,
                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',
              }}
            />
          )}
          {renderStatus && (
            <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: txTag,
                      status: tx.status || TransactionStatus.Idle,
                    })}
                  </Text>
                </Box>
              </Flex>
            </Box>
          )}
        </Box>
      );
    });

  useEffect(() => {
    const doneTxs = (transactions as any).filter((tx: any) => !!tx.txHash);
    setCanDismiss(!!doneTxs.length);
  }, [transactions]);

  const reset = () => {
    setCanDismiss(false);
    setIsFinalized(false);
    setTransactions([]);
    setCurrentTransaction(0);
    dispatch(transactionActions.clearTransactions());
  };

  const clearModalState = () => {
    setTimeout(() => {
      reset();
    }, 500);
  };

  const onDone = () => {
    const result = queueStatus.overall;

    dispatch(
      transactionActions.transactionsCompleted({
        result,
        queue,
        apiQueue,
        id: signingPackageId,
      })
    );
    dispatch(transactionActions.setConfirmationStatus(ConfirmationStatus.None));
    if (inFlight) dispatch(appStateActions.setInFlight(false));
    if (onCompleted) onCompleted(result, transactions);

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

    heap.click({ id: `${signingPackageId}_queue_modal_done_button` });
  };

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

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

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

  const dismiss = () => {
    setIsOpen(false);
    if (onDismissed) onDismissed();
    if (hasFailed) clearModalState();
  };

  return (
    <>
      <Button
        style={{
          position: 'fixed',
          top: 3,
          left: -15,
          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 || hasFailed) && (
            <>
              <Flex my="l">{renderHeader()}</Flex>
              {renderTransactions(transactions)}
              <Flex justifyContent="flex-end" mt="l" mb="xl">
                {(canDismiss || hasFailed) && (
                  <ButtonLarge onClick={dismiss}>
                    {canDismiss && !hasFailed
                      ? 'Finalize in background'
                      : 'Close'}
                  </ButtonLarge>
                )}
              </Flex>
            </>
          )}
          {(isOngoing || hasFailed) && !!txHistory.length && <Hr />}
          {!!txHistory.length && (
            <CollapsableCard
              variant="transparent"
              title="Earlier Transactions"
              mt="xl"
              defaultOpen
            >
              {txHistory.map((h) => (
                <Box key={h.id} mb="50px">
                  {renderTransactions(
                    h.apiQueue || h.queue?.transactions || [],
                    false
                  )}
                </Box>
              ))}
            </CollapsableCard>
          )}
        </Modal.Body>
      </ModalDrawer>
    </>
  );
};
