import { ApprovalType } from '@metamask/controller-utils';
import { CHAIN_IDS } from '../../shared/constants/network';
import { TransactionStatus } from '../../shared/constants/transaction';
import {
  unapprovedMessagesSelector,
  transactionsSelector,
  nonceSortedTransactionsSelector,
  nonceSortedPendingTransactionsSelector,
  nonceSortedCompletedTransactionsSelector,
  submittedPendingTransactionsSelector,
  hasTransactionPendingApprovals,
} from './transactions';

describe('Transaction Selectors', () => {
  describe('unapprovedMessagesSelector', () => {
    it('returns eth sign msg from unapprovedMsgs', () => {
      const msg = {
        id: 1,
        msgParams: {
          from: '0xAddress',
          data: '0xData',
          origin: 'origin',
        },
        time: 1,
        status: TransactionStatus.unapproved,
        type: 'eth_sign',
      };

      const state = {
        metamask: {
          unapprovedMsgs: {
            1: msg,
          },
          providerConfig: {
            chainId: '0x5',
          },
        },
      };

      const msgSelector = unapprovedMessagesSelector(state);

      expect(Array.isArray(msgSelector)).toStrictEqual(true);
      expect(msgSelector).toStrictEqual([msg]);
    });

    it('returns personal sign from unapprovedPersonalMsgsSelector', () => {
      const msg = {
        id: 1,
        msgParams: {
          from: '0xAddress',
          data: '0xData',
          origin: 'origin',
        },
        time: 1,
        status: TransactionStatus.unapproved,
        type: 'personal_sign',
      };

      const state = {
        metamask: {
          unapprovedPersonalMsgs: {
            1: msg,
          },
          providerConfig: {
            chainId: '0x5',
          },
        },
      };

      const msgSelector = unapprovedMessagesSelector(state);

      expect(Array.isArray(msgSelector)).toStrictEqual(true);
      expect(msgSelector).toStrictEqual([msg]);
    });

    it('returns typed message from unapprovedTypedMessagesSelector', () => {
      const msg = {
        id: 1,
        msgParams: {
          data: '0xData',
          from: '0xAddress',
          version: 'V3',
          origin: 'origin',
        },
        time: 1,
        status: TransactionStatus.unapproved,
        type: 'eth_signTypedData',
      };

      const state = {
        metamask: {
          unapprovedTypedMessages: {
            1: msg,
          },
          providerConfig: {
            chainId: '0x5',
          },
        },
      };

      const msgSelector = unapprovedMessagesSelector(state);

      expect(Array.isArray(msgSelector)).toStrictEqual(true);
      expect(msgSelector).toStrictEqual([msg]);
    });
  });

  describe('transactionsSelector', () => {
    it('selects the currentNetworkTxList', () => {
      const state = {
        metamask: {
          providerConfig: {
            nickname: 'mainnet',
            chainId: CHAIN_IDS.MAINNET,
          },
          featureFlags: {
            showIncomingTransactions: false,
          },
          selectedAddress: '0xAddress',
          currentNetworkTxList: [
            {
              id: 0,
              time: 0,
              txParams: {
                from: '0xAddress',
                to: '0xRecipient',
              },
            },
            {
              id: 1,
              time: 1,
              txParams: {
                from: '0xAddress',
                to: '0xRecipient',
              },
            },
          ],
        },
      };

      const orderedTxList = state.metamask.currentNetworkTxList.sort(
        (a, b) => b.time - a.time,
      );

      const selectedTx = transactionsSelector(state);

      expect(Array.isArray(selectedTx)).toStrictEqual(true);
      expect(selectedTx).toStrictEqual(orderedTxList);
    });
  });

  describe('nonceSortedTransactionsSelector', () => {
    it('returns transaction group nonce sorted tx from from selectedTxList wit', () => {
      const tx1 = {
        id: 0,
        time: 0,
        txParams: {
          from: '0xAddress',
          to: '0xRecipient',
          nonce: '0x0',
        },
      };

      const tx2 = {
        id: 1,
        time: 1,
        txParams: {
          from: '0xAddress',
          to: '0xRecipient',
          nonce: '0x1',
        },
      };

      const state = {
        metamask: {
          providerConfig: {
            nickname: 'mainnet',
            chainId: CHAIN_IDS.MAINNET,
          },
          selectedAddress: '0xAddress',
          featureFlags: {
            showIncomingTransactions: false,
          },
          currentNetworkTxList: [tx1, tx2],
        },
      };

      const expectedResult = [
        {
          nonce: '0x0',
          transactions: [tx1],
          initialTransaction: tx1,
          primaryTransaction: tx1,
          hasRetried: false,
          hasCancelled: false,
        },
        {
          nonce: '0x1',
          transactions: [tx2],
          initialTransaction: tx2,
          primaryTransaction: tx2,
          hasRetried: false,
          hasCancelled: false,
        },
      ];

      expect(nonceSortedTransactionsSelector(state)).toStrictEqual(
        expectedResult,
      );
    });
  });

  describe('Sorting Transactions Selectors', () => {
    const submittedTx = {
      id: 0,
      time: 0,
      txParams: {
        from: '0xAddress',
        to: '0xRecipient',
        nonce: '0x0',
      },
      status: TransactionStatus.submitted,
    };

    const unapprovedTx = {
      id: 1,
      time: 1,
      txParams: {
        from: '0xAddress',
        to: '0xRecipient',
        nonce: '0x1',
      },
      status: TransactionStatus.unapproved,
    };

    const approvedTx = {
      id: 2,
      time: 2,
      txParams: {
        from: '0xAddress',
        to: '0xRecipient',
        nonce: '0x2',
      },
      status: TransactionStatus.approved,
    };

    const confirmedTx = {
      id: 3,
      time: 3,
      txParams: {
        from: '0xAddress',
        to: '0xRecipient',
        nonce: '0x3',
      },
      status: TransactionStatus.confirmed,
    };

    const state = {
      metamask: {
        providerConfig: {
          nickname: 'mainnet',
          chainId: CHAIN_IDS.MAINNET,
        },
        selectedAddress: '0xAddress',
        featureFlags: {
          showIncomingTransactions: false,
        },
        currentNetworkTxList: [
          submittedTx,
          unapprovedTx,
          approvedTx,
          confirmedTx,
        ],
      },
    };

    it('nonceSortedPendingTransactionsSelector', () => {
      const expectedResult = [
        {
          nonce: submittedTx.txParams.nonce,
          transactions: [submittedTx],
          initialTransaction: submittedTx,
          primaryTransaction: submittedTx,
          hasRetried: false,
          hasCancelled: false,
        },
        {
          nonce: unapprovedTx.txParams.nonce,
          transactions: [unapprovedTx],
          initialTransaction: unapprovedTx,
          primaryTransaction: unapprovedTx,
          hasRetried: false,
          hasCancelled: false,
        },
        {
          nonce: approvedTx.txParams.nonce,
          transactions: [approvedTx],
          initialTransaction: approvedTx,
          primaryTransaction: approvedTx,
          hasRetried: false,
          hasCancelled: false,
        },
      ];

      expect(nonceSortedPendingTransactionsSelector(state)).toStrictEqual(
        expectedResult,
      );
    });

    it('nonceSortedCompletedTransactionsSelector', () => {
      const expectedResult = [
        {
          nonce: confirmedTx.txParams.nonce,
          transactions: [confirmedTx],
          initialTransaction: confirmedTx,
          primaryTransaction: confirmedTx,
          hasRetried: false,
          hasCancelled: false,
        },
      ];

      expect(nonceSortedCompletedTransactionsSelector(state)).toStrictEqual(
        expectedResult,
      );
    });

    it('submittedPendingTransactionsSelector', () => {
      const expectedResult = [submittedTx];
      expect(submittedPendingTransactionsSelector(state)).toStrictEqual(
        expectedResult,
      );
    });
  });

  describe('hasTransactionPendingApprovals', () => {
    const mockNetworkId = 'mockNetworkId';
    const mockedState = {
      metamask: {
        providerConfig: {
          chainId: mockNetworkId,
        },
        pendingApprovalCount: 2,
        pendingApprovals: {
          1: {
            id: '1',
            origin: 'origin',
            time: Date.now(),
            type: ApprovalType.WatchAsset,
            requestData: {},
            requestState: null,
          },
          2: {
            id: '2',
            origin: 'origin',
            time: Date.now(),
            type: ApprovalType.Transaction,
            requestData: {},
            requestState: null,
          },
        },
        unapprovedTxs: {
          2: {
            id: '2',
            chainId: mockNetworkId,
          },
        },
      },
    };

    it('should return true if there is a pending transaction on same network', () => {
      const result = hasTransactionPendingApprovals(mockedState);
      expect(result).toBe(true);
    });
    it('should return false if there is a pending transaction on different network', () => {
      mockedState.metamask.unapprovedTxs['2'].chainId = 'differentNetworkId';
      const result = hasTransactionPendingApprovals(mockedState);
      expect(result).toBe(false);
    });
    it.each([
      [ApprovalType.EthDecrypt],
      [ApprovalType.EthGetEncryptionPublicKey],
      [ApprovalType.EthSign],
      [ApprovalType.EthSignTypedData],
      [ApprovalType.PersonalSign],
    ])(
      'should return true if there is a pending transaction of %s type',
      (type) => {
        const result = hasTransactionPendingApprovals({
          ...mockedState,
          metamask: {
            ...mockedState.metamask,
            pendingApprovals: {
              2: {
                id: '2',
                origin: 'origin',
                time: Date.now(),
                type,
                requestData: {},
                requestState: null,
              },
            },
          },
        });
        expect(result).toBe(true);
      },
    );
  });
});
