import { renderHook, waitFor } from '@testing-library/react';
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import type { ReactNode } from 'react';
import { useVegaTransactionUpdater } from './use-vega-transaction-updater';
import {
  OrderTxUpdateDocument,
  TransactionEventDocument,
  WithdrawalBusEventDocument,
} from './__generated__/TransactionResult';
import type {
  OrderTxUpdateSubscription,
  OrderTxUpdateFieldsFragment,
  WithdrawalBusEventSubscription,
  WithdrawalBusEventFieldsFragment,
  TransactionEventSubscription,
  TransactionEventFieldsFragment,
} from './__generated__/TransactionResult';

import type { VegaTransactionStore } from './use-vega-transaction-store';
import {
  AssetStatus,
  BusEventType,
  OrderStatus,
  OrderTimeInForce,
  OrderType,
  Side,
  WithdrawalStatus,
} from '@vegaprotocol/types';

const render = (mocks?: MockedResponse[]) => {
  const wrapper = ({ children }: { children: ReactNode }) => (
    <MockedProvider mocks={mocks}>{children}</MockedProvider>
  );

  return renderHook(() => useVegaTransactionUpdater(), { wrapper });
};

const pubKey = 'pubKey';

jest.mock('./use-vega-wallet', () => ({
  useVegaWallet: () => ({
    pubKey,
  }),
}));

const mockWaitForWithdrawalApproval = jest.fn();

jest.mock('./wait-for-withdrawal-approval', () => ({
  waitForWithdrawalApproval: () => mockWaitForWithdrawalApproval(),
}));

const updateWithdrawal = jest.fn();
const updateOrder = jest.fn();
const updateTransactionResult = jest.fn();

const defaultState: Partial<VegaTransactionStore> = {
  updateWithdrawal,
  updateOrder,
  updateTransactionResult,
};

const mockTransactionStoreState = jest.fn<Partial<VegaTransactionStore>, []>();

jest.mock('./use-vega-transaction-store', () => ({
  useVegaTransactionStore: (
    selector: (state: Partial<VegaTransactionStore>) => void
  ) => selector(mockTransactionStoreState()),
}));

const orderUpdate: OrderTxUpdateFieldsFragment = {
  type: OrderType.TYPE_LIMIT,
  id: '9c70716f6c3698ac7bbcddc97176025b985a6bb9a0c4507ec09c9960b3216b62',
  status: OrderStatus.STATUS_ACTIVE,
  rejectionReason: null,
  createdAt: '2022-07-05T14:25:47.815283706Z',
  expiresAt: '2022-07-05T14:25:47.815283706Z',
  size: '10',
  price: '300000',
  timeInForce: OrderTimeInForce.TIME_IN_FORCE_GTC,
  side: Side.SIDE_BUY,
  marketId: 'market-id',
  __typename: 'OrderUpdate',
};
const mockedOrderUpdate: MockedResponse<OrderTxUpdateSubscription> = {
  request: {
    query: OrderTxUpdateDocument,
    variables: { partyId: pubKey },
  },
  result: {
    data: {
      orders: [orderUpdate],
    },
  },
};

const transactionResultBusEvent: TransactionEventFieldsFragment = {
  __typename: 'TransactionResult',
  partyId: pubKey,
  hash: 'hash',
  status: true,
  error: null,
};
const mockedTransactionResultBusEvent: MockedResponse<TransactionEventSubscription> =
  {
    request: {
      query: TransactionEventDocument,
      variables: { partyId: pubKey },
    },
    result: {
      data: {
        busEvents: [
          {
            type: BusEventType.TransactionResult,
            event: transactionResultBusEvent,
          },
        ],
      },
    },
  };
const withdrawalBusEvent: WithdrawalBusEventFieldsFragment = {
  id: '2fca514cebf9f465ae31ecb4c5721e3a6f5f260425ded887ca50ba15b81a5d50',
  status: WithdrawalStatus.STATUS_OPEN,
  amount: '100',
  asset: {
    __typename: 'Asset',
    id: 'asset-id',
    name: 'asset-name',
    symbol: 'asset-symbol',
    decimals: 2,
    status: AssetStatus.STATUS_ENABLED,
    source: {
      __typename: 'ERC20',
      contractAddress: '0x123',
    },
  },
  createdTimestamp: '2022-07-05T14:25:47.815283706Z',
  withdrawnTimestamp: '2022-07-05T14:25:47.815283706Z',
  txHash: '0x123',
  details: {
    __typename: 'Erc20WithdrawalDetails',
    receiverAddress: '0x123',
  },
  pendingOnForeignChain: false,
  __typename: 'Withdrawal',
};

const mockedWithdrawalBusEvent: MockedResponse<WithdrawalBusEventSubscription> =
  {
    request: {
      query: WithdrawalBusEventDocument,
      variables: { partyId: pubKey },
    },
    result: {
      data: {
        busEvents: [
          {
            event: withdrawalBusEvent,
          },
        ],
      },
    },
  };

describe('useVegaTransactionManager', () => {
  it('updates order on OrderTxUpdate', async () => {
    mockTransactionStoreState.mockReturnValue(defaultState);
    render([mockedOrderUpdate]);
    await waitFor(() => {
      expect(updateOrder).toHaveBeenCalledWith(orderUpdate);
    });
  });

  it('updates transaction on TransactionResultBusEvents', async () => {
    mockTransactionStoreState.mockReturnValue(defaultState);
    render([mockedTransactionResultBusEvent]);
    await waitFor(() => {
      expect(updateTransactionResult).toHaveBeenCalledWith(
        transactionResultBusEvent
      );
    });
  });

  it('updates withdrawal on WithdrawalBusEvents', async () => {
    mockTransactionStoreState.mockReturnValue(defaultState);
    const erc20WithdrawalApproval = {};
    mockWaitForWithdrawalApproval.mockResolvedValueOnce(
      erc20WithdrawalApproval
    );
    render([mockedWithdrawalBusEvent]);
    await waitFor(() => {
      expect(updateWithdrawal).toHaveBeenCalledWith(
        withdrawalBusEvent,
        erc20WithdrawalApproval
      );
    });
  });
});
