import { screen, waitFor } from '@testing-library/react';
import CustomPayment, { PaymentSpecificPaymentProps } from '../custom-payment';
import React from 'react';
import {
    openSelect,
    selectOption,
    triggerFilterChange,
} from 'lib/test-utils/select';
import { renderInAppContext } from '@theorchard/suite-testing';
import { useAccountDetailsForPayment } from 'src/apollo/queries/account';
import {
    useStatementPeriodCurrentPeriod,
    useStatementPeriodsList,
} from 'src/apollo/queries/statement-periods';
import { INELIGIBLE_ACCOUNT_ALERT_TEXT } from 'src/constants';
import {
    ABACUS_ACTION_STATUSES,
    ABACUS_ACTIONS,
} from '@theorchard/accounting-apps-shared';
import * as customPaymentTestHelpers from 'src/components/payment-group-form/custom-payment/__tests__/helpers';

jest.mock('src/apollo/queries/account', () => ({
    useAccountDetailsForPayment: jest.fn(),
    useBaseAccountsSearchQuery: () =>
        customPaymentTestHelpers.mockGetBaseAccounts,
}));

jest.mock('src/apollo/queries/statement-periods', () => ({
    useStatementPeriodCurrentPeriod: jest.fn(),
    useStatementPeriodsList: jest.fn(),
}));

jest.mock('@theorchard/suite-components', () => ({
    ...jest.requireActual('@theorchard/suite-components'),
    useToast: jest.fn(),
}));

describe('<CustomPayment>', () => {
    const defaultProps = {
        changeHandler: customPaymentTestHelpers.changeHandler,
        formData: {
            accountOption: '',
            contractId: '',
            activityStatementPeriodId: '',
            statementPeriodId: '',
            amount: '',
            withholdingTaxAmount: '',
            withholdingTaxRate: '',
            vatAmount: '',
            vatRate: '',
            paymentName: '',
            currency: customPaymentTestHelpers.currency,
        },
    };

    beforeEach(() => {
        jest.clearAllMocks();

        (useAccountDetailsForPayment as jest.Mock).mockImplementation(() => ({
            data: null,
            loading: false,
            error: null,
        }));

        (useStatementPeriodCurrentPeriod as jest.Mock).mockImplementation(
            () => ({
                data: null,
                loading: false,
                error: null,
            })
        );
    });

    afterEach(() => {
        jest.restoreAllMocks();
    });

    const render = (props?: Partial<PaymentSpecificPaymentProps>) => {
        return renderInAppContext(
            <CustomPayment {...defaultProps} {...props} />
        );
    };

    it('renders all fields and handles user interactions', async () => {
        customPaymentTestHelpers.mockGetBaseAccounts.mockImplementation(() =>
            Promise.resolve(customPaymentTestHelpers.baseAccountsMockResult)
        );

        (useStatementPeriodCurrentPeriod as jest.Mock).mockImplementation(
            () => customPaymentTestHelpers.currentPeriodMockResult
        );

        (useAccountDetailsForPayment as jest.Mock).mockImplementation(
            () => customPaymentTestHelpers.customPaymentAccountMockResult
        );

        (useStatementPeriodsList as jest.Mock).mockImplementation(
            () => customPaymentTestHelpers.statementPeriodsListMockResult
        );

        const { rerender } = render();

        await customPaymentTestHelpers.goThroughSelectAccountStep();

        rerender(
            <CustomPayment
                {...defaultProps}
                formData={{
                    ...defaultProps.formData,
                    accountOption:
                        customPaymentTestHelpers.accountDropdownOptions[0],
                }}
            />
        );

        expect(useStatementPeriodCurrentPeriod).toHaveBeenCalled();
        expect(useAccountDetailsForPayment).toHaveBeenCalledWith(
            customPaymentTestHelpers.accountDropdownOptions[0].value
        );
        expect(screen.queryByTestId('SkeletonLoader')).not.toBeInTheDocument();

        customPaymentTestHelpers.detailsControlsLabels.forEach(label => {
            expect(screen.getByText(label)).toBeInTheDocument();
        });
        expect(
            screen.getByTestId('withholdingTaxRate-segmentedInput')
        ).toHaveTextContent('%');
        expect(screen.getByTestId('vatRate-segmentedInput')).toHaveTextContent(
            '%'
        );

        await customPaymentTestHelpers.goThroughSelectCurrencyStep();
        await customPaymentTestHelpers.goThroughSelectContractStep();
        await customPaymentTestHelpers.fillTextField({
            label: 'amount',
            value: '150000',
        });
        await customPaymentTestHelpers.fillTextField({
            label: 'wht',
            value: '-100',
        });
        await customPaymentTestHelpers.fillTextField({
            label: 'WHT Rate',
            value: '10.5',
        });
        await customPaymentTestHelpers.fillTextField({
            label: 'vat',
            value: '23',
        });
        await customPaymentTestHelpers.fillTextField({
            label: 'VAT Rate',
            value: '20',
        });
        await customPaymentTestHelpers.goThroughSelectActivityStatementPeriod();
        await customPaymentTestHelpers.fillTextField({
            label: 'Payment Name',
            value: '23',
        });
    });

    const baseSetup = async () => {
        customPaymentTestHelpers.mockGetBaseAccounts.mockImplementation(() =>
            Promise.resolve(customPaymentTestHelpers.baseAccountsMockResult)
        );

        (useStatementPeriodCurrentPeriod as jest.Mock).mockImplementation(
            () => customPaymentTestHelpers.currentPeriodMockResult
        );

        (useAccountDetailsForPayment as jest.Mock).mockImplementation(
            () => customPaymentTestHelpers.customPaymentAccountMockResult
        );

        (useStatementPeriodsList as jest.Mock).mockImplementation(
            () => customPaymentTestHelpers.statementPeriodsListMockResult
        );

        const { rerender } = render();

        await customPaymentTestHelpers.goThroughSelectAccountStep();

        rerender(
            <CustomPayment
                {...defaultProps}
                formData={{
                    ...defaultProps.formData,
                    accountOption:
                        customPaymentTestHelpers.accountDropdownOptions[0],
                }}
            />
        );
    };

    it('do not allow type more than two decimal places in wht rate field', async () => {
        await baseSetup();
        const inputChanges = ['10.12', '10.123', '10.1234'];
        for (const inputChange of inputChanges) {
            await customPaymentTestHelpers.fillTextField({
                label: 'WHT Rate',
                value: inputChange,
            });
        }
        const rateCalls =
            customPaymentTestHelpers.changeHandler.mock.calls.filter(
                call => call[0].target.name === 'withholdingTaxRate'
            );
        expect(rateCalls).toHaveLength(1);
        expect(rateCalls[0][0].target.value).toBe('10.12');
    });

    it('do not allow type more than two decimal places in vat rate field', async () => {
        await baseSetup();
        const inputChanges = ['20.12', '20.123', '20.1234'];
        for (const inputChange of inputChanges) {
            await customPaymentTestHelpers.fillTextField({
                label: 'VAT Rate',
                value: inputChange,
            });
        }
        const rateCalls =
            customPaymentTestHelpers.changeHandler.mock.calls.filter(
                call => call[0].target.name === 'vatRate'
            );
        expect(rateCalls).toHaveLength(1);
        expect(rateCalls[0][0].target.value).toBe('20.12');
    });

    it.each([
        ['VAT Rate', 'vatRate'],
        ['WHT Rate', 'withholdingTaxRate'],
    ])('do not allow incorrect percents in %s field', async (label, field) => {
        await baseSetup();
        const inputChanges = [
            '',
            '0',
            '20.12',
            '99',
            '99.99',
            '100',
            '100.00',
            '100.01',
            '101.123',
            '100.01',
        ];
        for (const inputChange of inputChanges) {
            await customPaymentTestHelpers.fillTextField({
                label,
                value: inputChange,
            });
        }
        const rateCalls =
            customPaymentTestHelpers.changeHandler.mock.calls.filter(
                call => call[0].target.name === field
            );
        expect(rateCalls).toHaveLength(6);
        expect(rateCalls[5][0].target.value).toBe('100.00');
    });

    it('shows validation error for WHT Rate and VAT Rate fields', async () => {
        await baseSetup();
        const fieldsValidationErrors = {
            withholdingTaxRate: 'Invalid WHT Rate',
            vatRate: 'Invalid VAT Rate',
        };
        render({
            fieldsValidationErrors,
            formData: {
                ...defaultProps.formData,
                accountOption:
                    customPaymentTestHelpers.accountDropdownOptions[0],
            },
        });
        expect(screen.getByText('Invalid WHT Rate')).toBeInTheDocument();
        expect(screen.getByText('Invalid VAT Rate')).toBeInTheDocument();
    });

    it('do not allow type more than two decimal places in wht amount field', async () => {
        await baseSetup();

        const inputChanges = [
            '-150000.12',
            '150000.12',
            '150000.123',
            '150000.1234',
        ];

        for (const inputChange of inputChanges) {
            await customPaymentTestHelpers.fillTextField({
                label: 'wht',
                value: inputChange,
            });
        }

        // Verify that changeHandler was called twice with values '-150000.12' and '150000.12'
        const amountCalls =
            customPaymentTestHelpers.changeHandler.mock.calls.filter(call => {
                return call[0].target.name === 'withholdingTaxAmount';
            });

        expect(amountCalls).toHaveLength(2);
        expect(amountCalls[0][0].target.value).toBe('-150000.12');
        expect(amountCalls[1][0].target.value).toBe('150000.12');
    });

    it('do not allow type negative sign in vat amount field', async () => {
        await baseSetup();

        const inputChanges = ['-23', '23', '23.1', '23.12', '23.123'];

        for (const inputChange of inputChanges) {
            await customPaymentTestHelpers.fillTextField({
                label: 'amount',
                value: inputChange,
            });
        }

        // Verify that changeHandler was called three times with values '23', '23.1', and '23.12'
        const amountCalls =
            customPaymentTestHelpers.changeHandler.mock.calls.filter(call => {
                return call[0].target.name === 'amount';
            });

        expect(amountCalls).toHaveLength(3);
        expect(amountCalls[0][0].target.value).toBe('23');
        expect(amountCalls[1][0].target.value).toBe('23.1');
        expect(amountCalls[2][0].target.value).toBe('23.12');
    });

    describe('loading states', () => {
        afterEach(() => {
            jest.resetAllMocks();
        });

        const goThroughBasicStepsForLoadingStates = async () => {
            customPaymentTestHelpers.mockGetBaseAccounts.mockImplementation(
                () =>
                    Promise.resolve(
                        customPaymentTestHelpers.baseAccountsMockResult
                    )
            );

            const { rerender } = render();

            await customPaymentTestHelpers.goThroughSelectAccountStep();

            rerender(
                <CustomPayment
                    {...defaultProps}
                    formData={{
                        ...defaultProps.formData,
                        accountOption:
                            customPaymentTestHelpers.accountDropdownOptions[0],
                    }}
                />
            );
        };

        it('shows loader when account details are loading', async () => {
            (useAccountDetailsForPayment as jest.Mock).mockImplementation(
                () => ({
                    data: null,
                    loading: true,
                    error: null,
                })
            );

            await goThroughBasicStepsForLoadingStates();

            expect(screen.queryAllByTestId('SkeletonLoader')).toHaveLength(
                customPaymentTestHelpers.detailsControlsLabels.length
            );
        });

        it('shows loader when statement periods are loading', async () => {
            (useAccountDetailsForPayment as jest.Mock).mockImplementation(
                () => customPaymentTestHelpers.customPaymentAccountMockResult
            );

            (useStatementPeriodCurrentPeriod as jest.Mock).mockImplementation(
                () => ({
                    data: null,
                    loading: true,
                    error: null,
                })
            );

            await goThroughBasicStepsForLoadingStates();

            expect(screen.queryAllByTestId('SkeletonLoader')).toHaveLength(
                customPaymentTestHelpers.detailsControlsLabels.length
            );
        });
    });

    describe('error states', () => {
        const errorObj = new Error('Error fetching current statement period');
        const currentPeriodErrorMockResult = {
            data: null,
            loading: false,
            error: errorObj,
        };

        it('account is ineligible', async () => {
            (useAccountDetailsForPayment as jest.Mock).mockImplementation(
                () => ({
                    data: {
                        abacusAccount: {
                            ...customPaymentTestHelpers.abacusAccount,
                            accountPayee: {
                                actionStates: [
                                    {
                                        actionName:
                                            ABACUS_ACTIONS.PAYMENT_ELIGIBILITY,
                                        actionStatus:
                                            ABACUS_ACTION_STATUSES.RUNNING,
                                    },
                                    {
                                        actionName:
                                            ABACUS_ACTIONS.TAX_ELIGIBILITY,
                                        actionStatus:
                                            ABACUS_ACTION_STATUSES.COMPLETE,
                                    },
                                ],
                            },
                        },
                    },
                    loading: false,
                    error: null,
                })
            );

            customPaymentTestHelpers.mockGetBaseAccounts.mockImplementation(
                () =>
                    Promise.resolve(
                        customPaymentTestHelpers.baseAccountsMockResult
                    )
            );

            (useStatementPeriodCurrentPeriod as jest.Mock).mockImplementation(
                () => customPaymentTestHelpers.currentPeriodMockResult
            );

            (useStatementPeriodsList as jest.Mock).mockImplementation(
                () => customPaymentTestHelpers.statementPeriodsListMockResult
            );

            const { rerender } = render();

            await openSelect(customPaymentTestHelpers.accountDropdownTestId);

            triggerFilterChange(customPaymentTestHelpers.inputValue);

            await waitFor(() => {
                expect(
                    customPaymentTestHelpers.mockGetBaseAccounts
                ).toHaveBeenCalledWith(
                    customPaymentTestHelpers.inputValue.trim().toLowerCase(),
                    undefined,
                    100,
                    0
                );
            });

            selectOption(customPaymentTestHelpers.accountDropdownOptions[0]);

            rerender(
                <CustomPayment
                    {...defaultProps}
                    formData={{
                        ...defaultProps.formData,
                        accountOption:
                            customPaymentTestHelpers.accountDropdownOptions[0],
                    }}
                />
            );

            expect(
                screen.queryByTestId(
                    customPaymentTestHelpers.currencyDropdownTestId
                )
            ).not.toBeInTheDocument();

            expect(
                screen.getByText(INELIGIBLE_ACCOUNT_ALERT_TEXT)
            ).toBeInTheDocument();
        });

        it('shows error when current statement periods fail to load', async () => {
            (useAccountDetailsForPayment as jest.Mock).mockImplementation(
                () => customPaymentTestHelpers.customPaymentAccountMockResult
            );

            (useStatementPeriodCurrentPeriod as jest.Mock).mockImplementation(
                () => currentPeriodErrorMockResult
            );

            customPaymentTestHelpers.mockGetBaseAccounts.mockImplementation(
                () =>
                    Promise.resolve(
                        customPaymentTestHelpers.baseAccountsMockResult
                    )
            );

            const { rerender } = render();

            await customPaymentTestHelpers.goThroughSelectAccountStep();

            rerender(
                <CustomPayment
                    {...defaultProps}
                    formData={{
                        ...defaultProps.formData,
                        accountOption:
                            customPaymentTestHelpers.accountDropdownOptions[0],
                    }}
                />
            );

            expect(
                screen.getByText('Error fetching current statement period')
            ).toBeInTheDocument();
        });
    });
});
