import { 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,
  ApiQueueItem,
  ApiQueueItemType,
  TranslationNS,
} from './definition';

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

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

const getQueueStatus = (queue: any[] = []) => {
  const status: {
    total: number;
    state: { [key: string]: any };
    overall: TransactionStatus;
  } = {
    total: queue.length,
    state: {},
    overall: TransactionStatus.Idle,
  };
  queue.forEach((q: any) => {
    if (status.state[q.status]) status.state[q.status] += 1;
    else status.state[q.status] = 1;
  });
  let overall = TransactionStatus.Idle;
  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 = TransactionStatus.Running;
  } else if (
    status.state[TransactionStatus.Succeeded] &&
    status.state[TransactionStatus.Succeeded] === status.total
  ) {
    overall = TransactionStatus.Succeeded;
  } else if (status.state[TransactionStatus.Failed]) {
    overall = TransactionStatus.Failed;
  }
  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(TranslationNS.QueueModal);
  const [transactions, setTransactions] = useState<
    | PolymeshTransactionBase<unknown[], unknown[]>[]
    | ApiQueueItem<any>[]
    | undefined
  >(undefined);
  const [isFinalized, setIsFinalized] = useState(false);
  const [isOpen, setIsOpen] = useState(false);
  const [canDismiss, setCanDismiss] = 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 targetQueue = apiQueue || queue?.transactions || [];
  const queueStatus = getQueueStatus(transactions);
  const isOngoing =
    !isFinalized && queueStatus.overall === TransactionStatus.Running;
  const hasFailed = !!queueStatus.state[TransactionStatus.Failed];
  const isFinalizedWithFail = isFinalized && hasFailed;
  const showMainTx = isOngoing || hasFailed;

  const clearModalState = () => {
    setIsFinalized(false);
    setCanDismiss(false);
    setTransactions(undefined);
    dispatch(transactionActions.clearTransactions());
  };

  const reset = () => {
    if (signingPackage.apiQueue) {
      signingPackage.apiQueue = signingPackage.apiQueue.filter(
        (e: ApiQueueItem<any>) =>
          e.status &&
          [TransactionStatus.Succeeded, TransactionStatus.Failed].includes(
            e.status,
          ),
      );
    }
    setTxHistory([signingPackage, ...txHistory]);
    setTimeout(() => {
      clearModalState();
    }, 500);
  };

  const onDone = () => {
    setIsOpen(false);

    const result = TransactionStatus.Succeeded;

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

    reset();
  };

  const onDismiss = () => {
    setIsOpen(false);
    if (onDismissed) onDismissed();
    if (isFinalizedWithFail) reset();
  };

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

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

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

  const processApiQueue = useCallback(() => {
    let currentTxIndex = 0;

    const setCurrentTxState = (state: Partial<ApiQueueItem<any>>) => {
      signingPackage.apiQueue[currentTxIndex] = {
        ...signingPackage.apiQueue[currentTxIndex],
        ...state,
      };
      const txs = transactions || [];
      const tempQueue = [...txs] as ApiQueueItem<any>[];
      tempQueue.splice(currentTxIndex, 1, {
        ...txs[currentTxIndex],
        ...state,
      } as any);
      setTransactions([...tempQueue]);
    };

    const processCurrentTx = async () => {
      const txs = transactions || [];
      const tx = txs[currentTxIndex] as ApiQueueItem<any>;
      try {
        setCurrentTxState({ 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 };

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

        if (isError && tx.blocking) {
          setIsFinalized(true);
        } else if (currentTxIndex < txs.length - 1) {
          currentTxIndex += 1;
          processCurrentTx();
        } else {
          setIsFinalized(true);
          if (!hasFailed) onDone();
        }
      } catch (e) {
        if (e.message === 'Cancelled') {
          setCurrentTxState({ status: TransactionStatus.Rejected });
        } else {
          setCurrentTxState({ status: TransactionStatus.Failed });
        }
      }
    };

    processCurrentTx();
  }, [hasFailed, sdk, signAndSend, transactions]); // eslint-disable-line

  // Kick-off on confirmation
  useEffect(() => {
    const runQueue = async () => {
      setTransactions(targetQueue);
      if (queue) {
        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);
    }
  }, [confirmationStatus]); // eslint-disable-line

  // Kick-off API queue processing after transactions are set
  useEffect(() => {
    if (
      apiQueue &&
      transactions &&
      transactions[0]?.status === TransactionStatus.Idle &&
      !isFinalized
    ) {
      processApiQueue();
    }
  }, [transactions, isFinalized, processApiQueue, apiQueue]);

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

  const renderStatusIcon = (status: TransactionStatus) => {
    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 (
          <IconOutlined
            Asset={icons.SvgClose}
            width={24}
            height={24}
            scale={0.75}
            color="alert"
          />
        );
      case TransactionStatus.Succeeded:
        return (
          <IconOutlined
            Asset={icons.SvgCheckmark}
            width={24}
            height={24}
            scale={0.75}
            color="success"
          />
        );
    }
  };

  const getText = ({
    id,
    text,
    tag,
    status,
  }: {
    id: string;
    text: string;
    tag?: string;
    status: TransactionStatus;
  }) => {
    const value = t(
      `${id}.${`${tag ? `${tag.replace(/\./g, '_')}.` : ''}`}${text}.${status}`,
    );
    const fail = (
      <>
        {value}
        {failUrl && text === 'status' && (
          <>
            {' '}
            <Link href={failUrl}>{t('General:learnMore')}</Link>
          </>
        )}
      </>
    );
    if (status === TransactionStatus.Failed) return fail;
    return value;
  };

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

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

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

  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%">
          {!showMainTx && !txHistory.length && (
            <Box mt="xl">
              <Text variant="b1m" color="gray.1">
                No transactions
              </Text>
            </Box>
          )}
          {showMainTx && (
            <>
              <Flex my="l">{renderHeader(signingPackageId)}</Flex>
              {renderTransactions(signingPackageId, transactions || [])}
              <Flex justifyContent="flex-end" mt="l" mb="xl">
                {(canDismiss || isFinalizedWithFail) && (
                  <ButtonLarge
                    id="queue_modal_dismiss_button"
                    onClick={onDismiss}
                  >
                    {canDismiss && !isFinalized
                      ? 'Finalize in background'
                      : 'Close'}
                  </ButtonLarge>
                )}
              </Flex>
            </>
          )}
          {showMainTx && !!txHistory.length && <Hr />}
          {!!txHistory.length && (
            <CollapsableCard
              variant="transparent"
              title="Earlier Transactions"
              mt="xl"
              defaultOpen
            >
              {txHistory.map((h, i) => (
                <Box key={`${h.id}-${i.toString()}`} mb="50px">
                  {renderTransactions(
                    h.id,
                    h.apiQueue || h.queue?.transactions || [],
                    false,
                  )}
                </Box>
              ))}
            </CollapsableCard>
          )}
        </Modal.Body>
      </ModalDrawer>
    </>
  );
};
