import React from 'react';
import { fireEvent, screen, waitFor } from '@testing-library/react';
import { renderInAppContext } from '@theorchard/suite-testing';
import paymentAccounts from 'src/__fixtures__/graphql/payment-group-payment-accounts-response.json';
import * as paymentGroupPaymentAccountMutation from 'src/apollo/mutations/payment-group-payment-account';
import * as paymentGroupPaymentAccountQuery from 'src/apollo/queries/payment-group-payment-account';
import PaymentGroupPaymentAccounts, {
    type PaymentGroupPaymentAccountsProps,
} from 'src/components/payment-group-detail/payment-group-payment-accounts';
import { CONFIRM_FUNDS, PAYMENT_APPROVAL_WARNING_MSG } from 'src/constants';
import { DEFAULT_ITEMS_PER_PAGE } from 'src/constants';

const componentInContext = (props: PaymentGroupPaymentAccountsProps) => (
    <PaymentGroupPaymentAccounts {...props} />
);

jest.mock('react-router', () => ({
    ...jest.requireActual('react-router'),
    useLocation: jest.fn().mockImplementation(() => {
        return { search: 'searchTerm=Test+Search+Term', pathname: '/' };
    }),
}));

const optionalEventProperties = {
    abacusEventId: '',
    createdBy: '',
    eventDate: '',
    statementPeriodId: null,
};

describe('<PaymentGroupPaymentAccounts />', () => {
    const actionStates = {
        approve: {
            actionName: 'approve',
            actionStatus: 'init',
            abacusStateId: '',
            message: '',
        },
        generate_payments: {
            actionName: 'generate_payments',
            actionStatus: 'complete',
            abacusStateId: '',
            message: '',
        },
        post: {
            actionName: 'post',
            actionStatus: 'init',
            abacusStateId: '',
            message: '',
        },
        send_payments: {
            actionName: 'send_payments',
            actionStatus: 'init',
            abacusStateId: '',
            message: '',
        },
    };
    const deleteMock = jest.fn().mockResolvedValue({
        data: { abacusSoftDeletePaymentGroupPaymentAccount: { deleted: true } },
    });
    const { items } = paymentAccounts.abacusPaymentGroupPaymentAccounts;
    const componentProps: PaymentGroupPaymentAccountsProps = {
        abacusEvents: [],
        actionStates,
        setErrors: jest.fn(),
        statementPeriod: {
            statementPeriodName: 'June 2021',
        },
    };
    const updateMock = jest.fn().mockResolvedValue({
        data: {
            abacusUpdatePaymentGroupPaymentAccount: { items, totalCount: 2 },
        },
    });
    let deleteSpy: jest.SpyInstance;
    let requestSpy: jest.SpyInstance;
    let updateSpy: jest.SpyInstance;

    const render = (props: PaymentGroupPaymentAccountsProps) =>
        renderInAppContext(componentInContext(props));

    afterEach(jest.restoreAllMocks);
    beforeEach(() => {
        deleteSpy = jest
            .spyOn(
                paymentGroupPaymentAccountMutation,
                'useDeletePaymentGroupPaymentAccount'
            )
            .mockReturnValue(deleteMock);
        requestSpy = jest
            .spyOn(
                paymentGroupPaymentAccountQuery,
                'usePaymentGroupPaymentAccounts'
            )
            .mockReturnValue({
                data: paymentAccounts,
                loading: false,
                error: undefined,
            });
        updateSpy = jest
            .spyOn(
                paymentGroupPaymentAccountMutation,
                'useUpdatePaymentGroupPaymentAccount'
            )
            .mockReturnValue({
                updatePaymentGroupPaymentAccount: updateMock,
                loading: false,
            });
    });

    it('requests a list of paymentGroupPaymentAccounts on render', () => {
        render(componentProps);
        expect(requestSpy).toHaveBeenCalled();
    });

    it('renders a list of paymentGroupPaymentAccounts', () => {
        render(componentProps);
        items.forEach(item => {
            expect(screen.getByText(item.account.accountName)).toBeTruthy();
            expect(
                screen.getByText(item.currencyCode, { exact: false })
            ).toBeTruthy();
            expect(
                screen.getByText(item.lastPayment, { exact: false })
            ).toBeTruthy();
            expect(
                screen.getByText(item.currentBalance, { exact: false })
            ).toBeTruthy();
            expect(
                screen.getByText(item.percentDifference, { exact: false })
            ).toBeTruthy();

            /* eslint-disable jest/no-conditional-expect */
            if (parseFloat(item.paymentDifference) < 0)
                expect(
                    screen.getByText(item.paymentDifference.slice(1), {
                        exact: false,
                    })
                ).toBeTruthy();
            else
                expect(
                    screen.getByText(item.paymentDifference, { exact: false })
                ).toBeTruthy();

            if (item.note) expect(screen.getByText(item.note)).toBeTruthy();
            /* eslint-enable jest/no-conditional-expect */
        });
    });

    it('renders a table with sortable headers', () => {
        render(componentProps);
        const headers = screen.getAllByRole('columnheader');
        const headersText = headers.map(header => header.textContent);
        expect(headersText).toEqual([
            'Account Name',
            'Account Id',
            'Currency',
            'Last Payment',
            'Outstanding Balance',
            'Tax Withholding',
            'Last Payment Statement Period',
            'June 2021 Payment',
            'Diff',
            'Diff %',
            'Note',
            'Status',
            'Error Message',
            '',
        ]);
        expect(headers[0].querySelector('.ChevronDownGlyphIcon')).toBeTruthy();
        fireEvent.click(headers[0]);
        expect(headers[0].querySelector('.ChevronUpGlyphIcon')).toBeTruthy();
    });

    it('adds styling to positive and negative paymentDifferences', () => {
        render(componentProps);
        const paymentDiffs = items.map(item => item.paymentDifference);
        const negativeDiff = paymentDiffs.find(diff => parseFloat(diff) < 0);
        const positiveDiff = paymentDiffs.find(diff => parseFloat(diff) > 0);

        expect(
            screen.getByText(negativeDiff?.slice(1) ?? '', { exact: false })
        ).toEqual(screen.getAllByTestId('payment-diff-red')[0]);
        expect(screen.getByText(positiveDiff ?? '', { exact: false })).toEqual(
            screen.getAllByTestId('payment-diff-green')[0]
        );
    });

    it('renders links to the account details page', () => {
        render(componentProps);
        const accounts = items.map(item => item.account);

        accounts.forEach(account => {
            const { accountId, accountName } = account;
            // @ts-expect-error: href does exist on Element
            expect(screen.getByText(accountName).href).toContain(
                `/account/${accountId}`
            );
        });
    });

    it('renders a list of paymentGroupPaymentAccounts with paymentStatus and paymentErrorCode', () => {
        let accounts = paymentAccounts.abacusPaymentGroupPaymentAccounts.items;
        const count =
            paymentAccounts.abacusPaymentGroupPaymentAccounts.totalCount;
        accounts = [
            {
                ...accounts[0],
                paymentStatus: 'Canceled',
                paymentErrorCode: 'Payment Rejected',
            },
            {
                ...accounts[1],
                paymentStatus: 'Failure',
                paymentErrorCode: 'Error',
            },
        ];
        jest.spyOn(
            paymentGroupPaymentAccountQuery,
            'usePaymentGroupPaymentAccounts'
        ).mockReturnValue({
            data: {
                abacusPaymentGroupPaymentAccounts: {
                    items: accounts,
                    totalCount: count,
                },
            },
            loading: false,
            error: undefined,
        });
        render(componentProps);
        accounts.forEach(item => {
            expect(screen.getByText(item.paymentStatus)).toBeTruthy();
            expect(screen.getByText(item.paymentErrorCode)).toBeTruthy();
        });
    });
    describe('account search filters', () => {
        const defaultRequestSpyParams = {
            limit: DEFAULT_ITEMS_PER_PAGE,
            offset: 0,
            sortBy: 'account_name',
            sortOrder: 'asc',
            searchTerm: '',
        };

        it('renders', () => {
            render(componentProps);

            expect(screen.getByText('Account')).toBeDefined();
            expect(
                screen.getByPlaceholderText('Filter by Account Name or ID')
            ).toBeDefined();
            expect(screen.getByTestId('SearchFieldIcon')).toBeDefined();
        });

        it('account search filter applied', () => {
            render(componentProps);

            const input = screen.getByTestId('searchFieldInput');

            expect(input).toHaveDisplayValue('Test Search Term');
            expect(screen.getByText('Clear Filters')).toBeDefined();
            expect(screen.getByTestId('CloseGlyphIcon')).toBeDefined();
            expect(screen.getByText('Results:')).toBeDefined();
            expect(requestSpy).toHaveBeenCalledWith({
                ...defaultRequestSpyParams,
                searchTerm: 'Test Search Term',
            });
        });

        it('clear account search filters', () => {
            render(componentProps);

            const clearFilters = screen.getByText('Clear Filters');

            fireEvent.click(clearFilters);
            expect(requestSpy).toHaveBeenCalledWith(defaultRequestSpyParams);
        });
    });

    describe('deleting an account from a payment group', () => {
        it('renders delete icons if payments are not sent & there is more than one account', () => {
            render(componentProps);
            const deleteButtons = screen.getAllByTestId(
                'payment-account-delete-span'
            );
            expect(deleteButtons.length).toBeGreaterThan(0);
        });

        it('does not render delete icons if send_payment state is running', () => {
            const newStates = {
                ...actionStates,
                send_payments: {
                    actionName: 'send_payments',
                    actionStatus: 'running',
                    abacusStateId: '',
                    message: '',
                },
            };
            render({ ...componentProps, actionStates: newStates });
            const deleteButtons = screen.queryAllByTestId(
                'payment-account-delete-span'
            );
            expect(deleteButtons.length).toEqual(0);
        });

        it('calls the delete mutation when delete icon is clicked', async () => {
            render(componentProps);

            const deleteButtons = screen.getAllByTestId(
                'payment-account-delete-span'
            );
            fireEvent.click(deleteButtons[1]);

            const confirmButton = screen.getByText('YES');
            fireEvent.click(confirmButton);

            await waitFor(() => {
                expect(deleteSpy).toHaveBeenCalled();
            });
        });

        it('shows a message after successful deletion', async () => {
            render(componentProps);

            const deleteButtons = screen.getAllByTestId(
                'payment-account-delete-span'
            );
            fireEvent.click(deleteButtons[1]);

            const confirmButton = screen.getByText('YES');
            fireEvent.click(confirmButton);

            await waitFor(() => {
                expect(
                    screen.getByText('was removed', { exact: false })
                ).toBeTruthy();
            });
        });
    });

    describe('adding a note to an account in the payment group', () => {
        it('renders with an ADD NOTE button if account does not have a note', () => {
            render(componentProps);
            expect(screen.getByText('Add Note')).toBeTruthy();
        });

        it('renders with an disabled ADD NOTE button if payment was sent', () => {
            const newStates = {
                ...actionStates,
                send_payments: {
                    actionName: 'send_payments',
                    actionStatus: 'running',
                    abacusStateId: '',
                    message: '',
                },
            };
            render({ ...componentProps, actionStates: newStates });
            const addNotes = screen.getAllByText('Add Note');
            expect(addNotes.length).toEqual(1);
            expect(addNotes[0]).toHaveProperty('disabled', true);
        });

        it('opens a modal when ADD NOTE button is clicked', () => {
            render(componentProps);
            const addNoteButton = screen.getByText('Add Note');
            fireEvent.click(addNoteButton);
            expect(
                screen.getByTestId('payment-account-note-input')
            ).toBeTruthy();
        });

        it('calls the update mutation when a note is added', async () => {
            render(componentProps);

            const addNoteButton = screen.getByText('Add Note');
            fireEvent.click(addNoteButton);

            const saveButton = screen.getByText('Add');
            const textInput = screen.getByTestId('payment-account-note-input');

            fireEvent.change(textInput, {
                target: { value: 'non-empty string to enable the Add button' },
            });

            fireEvent.click(saveButton);

            await waitFor(() => {
                expect(updateSpy).toHaveBeenCalled();
            });
        });
    });

    describe('on funds confirmation, show approval reset warning message popup', () => {
        it('show approval reset warning message when delete icon is clicked', () => {
            render({
                ...componentProps,
                abacusEvents: [
                    {
                        eventName: CONFIRM_FUNDS.EVENT_NAME,
                        ...optionalEventProperties,
                    },
                ],
            });
            const deleteButtons = screen.getAllByTestId(
                'payment-account-delete-span'
            );
            fireEvent.click(deleteButtons[1]);

            expect(PAYMENT_APPROVAL_WARNING_MSG).toBeDefined();
        });

        it('show approval reset warning message when ADD NOTE button is clicked', () => {
            render({
                ...componentProps,
                abacusEvents: [
                    {
                        eventName: CONFIRM_FUNDS.EVENT_NAME,
                        ...optionalEventProperties,
                    },
                ],
            });
            const addNoteButton = screen.getByText('Add Note');
            fireEvent.click(addNoteButton);

            expect(PAYMENT_APPROVAL_WARNING_MSG).toBeDefined();
        });

        it('calls the delete mutation when the YES button of warning popup is clicked', () => {
            render({
                ...componentProps,
                abacusEvents: [
                    {
                        eventName: CONFIRM_FUNDS.EVENT_NAME,
                        ...optionalEventProperties,
                    },
                ],
            });
            const deleteButtons = screen.getAllByTestId(
                'payment-account-delete-span'
            );
            fireEvent.click(deleteButtons[1]);
            expect(PAYMENT_APPROVAL_WARNING_MSG).toBeDefined();

            const confirmButton = screen.getByText('Yes');
            fireEvent.click(confirmButton);

            expect(deleteSpy).toHaveBeenCalled();
        });
    });
});
