import React from 'react';
import { ApolloError } from '@apollo/client';
import { fireEvent, screen, waitFor } from '@testing-library/react';
import { renderInAppContext } from '@theorchard/suite-testing';
import { contractAdvancesQualifiedList } from 'src/__fixtures__/graphql/contract-advances';
import * as contractQuery from 'src/apollo/queries/contract';
import * as contractAdvanceQuery from 'src/apollo/queries/contract-advance';
import PaymentSpecificAdvance from 'src/components/payment-group-form/payment-specific-advance';
import { INELIGIBLE_ACCOUNT_ALERT_TEXT } from 'src/constants';
import { ContractTermContext } from 'src/contexts/contract-term-context';
import { initialTermState } from 'src/reducers/contract-term/contract-term-reducer';
import type { GetContractAdvancesQualifiedQuery } from 'src/apollo/queries/contract-advance/__generated__/get-contract-advances-qualified-list';

interface ContextValues {
    state: { contract: { contractId: string; contractName: string } };
    dispatch: jest.Mock;
}

interface ContractAdvanceSpyInstance {
    data: GetContractAdvancesQualifiedQuery | undefined;
    error: ApolloError | undefined;
    loading: boolean;
}

describe('<PaymentSpecificAccount />', () => {
    let contractRequestSpy: jest.SpyInstance;
    let contractAdvanceSpy: jest.SpyInstance<ContractAdvanceSpyInstance>;
    let contractAccountPayeeActionStatesRequestSpy: jest.SpyInstance;

    const props = {
        changeHandler: jest.fn(),
        formData: {
            contractAdvancePendingId: '111',
            specificAdvance: {
                paymentName: 'Test Advance Name',
            },
        },
        setFormData: jest.fn(),
        parsedParams: {
            contractId: '',
            contractAdvanceId: '',
            referencePaymentTypeId: '',
        },
    };

    const mockIdentity = {
        id: '0ee610b7-f3d1-56ba-a562-9f645a337f41',
        features: {},
    };

    const render = (
        contextValues: ContextValues,
        identity?: { id: string; features: { [x: string]: boolean } }
    ) =>
        renderInAppContext(
            <ContractTermContext.Provider value={contextValues}>
                <PaymentSpecificAdvance {...props} />
            </ContractTermContext.Provider>,
            { identity }
        );

    const mockContractResult = {
        abacusContracts: {
            totalCount: 1,
            items: [
                {
                    contractId: '123',
                    contractName: 'Test contract name',
                    contractType: 'neighbouring_rights',
                    account: {
                        accountId: '1',
                        accountName: 'Test account name',
                    },
                },
            ],
        },
    };

    const mockActionStates = [
        {
            abacusStateId: '1',
            actionName: 'payment_eligibility',
            actionStatus: 'approved',
            message: 'test message',
        },
        {
            abacusStateId: '2',
            actionName: 'tax_eligibility',
            actionStatus: 'complete',
            message: 'test message',
        },
    ];

    beforeEach(() => {
        contractRequestSpy = jest
            .spyOn(contractQuery, 'useContractSearchQuery')
            .mockReturnValue(
                async () =>
                    await Promise.resolve(
                        mockContractResult.abacusContracts.items
                    )
            );
        contractAccountPayeeActionStatesRequestSpy = jest
            .spyOn(contractQuery, 'useContractAccountPayeeActionStates')
            .mockReturnValue({
                data: mockActionStates,
                loading: false,
                error: undefined,
                getActionStatesByContractId: jest.fn().mockResolvedValue({
                    data: mockActionStates,
                }),
            });
        contractAdvanceSpy = jest
            .spyOn(contractAdvanceQuery, 'useContractAdvancesQualified')
            .mockReturnValue({
                data: contractAdvancesQualifiedList as GetContractAdvancesQualifiedQuery,
                loading: false,
                error: undefined,
            });
    });

    const contextValues = {
        state: {
            ...initialTermState,
            contract: {
                contractId: '1',
                contractName: 'AGR225807 - Gottwald, Lukasz Neighbouring Right',
            },
        },
        dispatch: jest.fn(),
    };

    it('renders Contract search', () => {
        const { container } = render(contextValues);

        expect(screen.getByText('Search By Contract Name or ID')).toBeDefined();
        expect(screen.getByTestId('PaymentSpecificAdvance')).toBeDefined();
        expect(container.getElementsByClassName('row').length).toBe(0);
    });

    it('searches contracts', async () => {
        render(contextValues);

        const contractSearchSelect = screen.getAllByRole('combobox')[0];
        fireEvent.change(contractSearchSelect, { target: { value: 'Test' } });
        await waitFor(() => {
            expect(screen.getByText('Test contract name - 123')).toBeDefined();
        });
        expect(contractRequestSpy).toHaveBeenCalled();
    });

    it('shows Select Unpaid Advance and Payment Name after contract search and select', async () => {
        render(contextValues);

        const contractSearchSelect = screen.getAllByRole('combobox')[0];
        fireEvent.change(contractSearchSelect, { target: { value: 'Test' } });
        await waitFor(() => {
            expect(screen.getByText('Test contract name - 123')).toBeDefined();
        });
        expect(contractRequestSpy).toHaveBeenCalled();

        const searchResulElement = screen.getByText('Test contract name - 123');
        fireEvent.click(searchResulElement);
        expect(screen.getByText('Select Unpaid Advance')).toBeDefined();
        const contractAdvancePendingDropdown = screen.getByText(
            'Test Advance 3 | $789,657,896.00'
        );
        expect(contractAdvanceSpy).toHaveBeenCalled();

        fireEvent.click(contractAdvancePendingDropdown);

        if (contractAdvancePendingDropdown) {
            fireEvent.focus(contractAdvancePendingDropdown);
            fireEvent.keyDown(contractAdvancePendingDropdown, {
                key: 'ArrowDown',
                code: 40,
            });
        }
        expect(
            screen.getByText('Advance Description Payment | $78,921.98')
        ).toBeDefined();
        expect(screen.getByText('Test Advance | $1,289.89')).toBeDefined();

        fireEvent.click(
            screen.getByTestId('Dropdown_ContractAdvancePendingDropdown')
        );

        await waitFor(() => {
            expect(props.changeHandler).toHaveBeenCalledWith({
                target: {
                    name: 'specificAdvance',
                    value: {
                        contractAdvanceId: '3',
                        paymentName: 'Test Advance 3',
                    },
                },
            });
        });

        expect(screen.getByTestId('input-payment-name')).toHaveDisplayValue(
            'Test Advance Name'
        );
    });

    it('select advance with eligible account', async () => {
        render(contextValues, mockIdentity);

        const contractSearchSelect = screen.getAllByRole('combobox')[0];
        fireEvent.change(contractSearchSelect, { target: { value: 'Test' } });
        await waitFor(() => {
            expect(screen.getByText('Test contract name - 123')).toBeDefined();
        });
        expect(contractRequestSpy).toHaveBeenCalled();

        expect(contractAccountPayeeActionStatesRequestSpy).toHaveBeenCalled();

        const searchResulElement = screen.getByText('Test contract name - 123');
        fireEvent.click(searchResulElement);
        expect(screen.getByText('Select Unpaid Advance')).toBeDefined();
        const contractAdvancePendingDropdown = screen.getByText(
            'Test Advance 3 | $789,657,896.00'
        );
        expect(contractAdvanceSpy).toHaveBeenCalled();

        fireEvent.click(contractAdvancePendingDropdown);

        if (contractAdvancePendingDropdown) {
            fireEvent.focus(contractAdvancePendingDropdown);
            fireEvent.keyDown(contractAdvancePendingDropdown, {
                key: 'ArrowDown',
                code: 40,
            });
        }
        expect(
            screen.getByText('Advance Description Payment | $78,921.98')
        ).toBeDefined();
        expect(screen.getByText('Test Advance | $1,289.89')).toBeDefined();

        fireEvent.click(
            screen.getByTestId('Dropdown_ContractAdvancePendingDropdown')
        );

        await waitFor(() => {
            expect(props.changeHandler).toHaveBeenCalledWith({
                target: {
                    name: 'specificAdvance',
                    value: {
                        contractAdvanceId: '3',
                        paymentName: 'Test Advance 3',
                    },
                },
            });
        });

        expect(screen.getByTestId('input-payment-name')).toHaveDisplayValue(
            'Test Advance Name'
        );
    });

    it('select advance with ineligible account', async () => {
        contractAccountPayeeActionStatesRequestSpy = jest
            .spyOn(contractQuery, 'useContractAccountPayeeActionStates')
            .mockReturnValue({
                data: mockActionStates.map(item => ({
                    ...item,
                    actionStatus: 'init',
                })),
                loading: false,
                error: undefined,
                getActionStatesByContractId: jest.fn().mockResolvedValue({
                    data: mockActionStates.map(item => ({
                        ...item,
                        actionStatus: 'init',
                    })),
                }),
            });

        render(contextValues, mockIdentity);

        const contractSearchSelect = screen.getAllByRole('combobox')[0];
        fireEvent.change(contractSearchSelect, { target: { value: 'Test' } });
        await waitFor(() => {
            expect(screen.getByText('Test contract name - 123')).toBeDefined();
        });
        expect(contractRequestSpy).toHaveBeenCalled();

        const searchResulElement = screen.getByText('Test contract name - 123');
        fireEvent.click(searchResulElement);
        expect(screen.queryByText('Select Unpaid Advance')).toBeNull();
        expect(
            await screen.findByText(INELIGIBLE_ACCOUNT_ALERT_TEXT)
        ).toBeInTheDocument();
    });

    it('if No Advances are pending for selected contract', async () => {
        contractAdvanceSpy = jest
            .spyOn(contractAdvanceQuery, 'useContractAdvancesQualified')
            .mockReturnValue({
                data: {
                    abacusContractAdvancesQualified: {
                        items: [],
                        totalCount: 0,
                    },
                },
                loading: false,
                error: undefined,
            });
        ((props.formData = {
            contractAdvancePendingId: '321',
            specificAdvance: {
                paymentName: '',
            },
        }),
            render(contextValues));

        const contractSearchSelect = screen.getAllByRole('combobox')[0];
        fireEvent.change(contractSearchSelect, { target: { value: 'Test' } });
        await waitFor(() => {
            expect(screen.getByText('Test contract name - 123')).toBeDefined();
        });

        fireEvent.click(screen.getByText('Test contract name - 123'));

        expect(
            screen.getByText('No Advances that are not paid for this contract.')
        ).toBeDefined();
        expect(screen.getByTestId('input-payment-name')).toBeDefined();
        expect(screen.getByPlaceholderText('Enter Payment Name')).toBeDefined();
    });
});
