import React from 'react';
import { fireEvent, screen, waitFor } from '@testing-library/react';
import { renderInAppContext } from '@theorchard/suite-testing';
import { createMemoryHistory } from 'history';
import { Router } from 'react-router-dom';
import { referenceFlowthroughCalculations } from 'src/__fixtures__/graphql/reference-flowthrough-calculations';
import * as getReferenceFlowthroughCalculationQuery from 'src/apollo/queries/reference-flowthrough-calculation';
import {
    CONTRACT_FLOWTHROUGH_RATE_ERROR_MSG,
    CONTRACT_FLOWTHROUGH_RECOUPMENT_CAP_ERROR_MSG,
} from 'src/constants';
import * as createContractFlowthroughHook from 'src/hooks/contract-flowthrough/create-contract-flowthrough';
import { ContractFlowthroughFullScreenModal } from '../contract-flowthrough-fullscreen-modal';

describe('ContractFlowthroughFullscreenModal', () => {
    const history = createMemoryHistory();
    jest.spyOn(history, 'push');
    const render = () => {
        renderInAppContext(
            <Router history={history}>
                <ContractFlowthroughFullScreenModal />
            </Router>
        );
        return history;
    };

    const selectCalculationWithRate = () => {
        const calculationSelect = screen.getByTestId('SuiteSelectInputValue');
        fireEvent.click(calculationSelect);
        fireEvent.click(screen.getByText('Gross Revenue * FT%'));
    };

    const selectCalculationWithRecoupmentCap = () => {
        const calculationSelect = screen.getByTestId('SuiteSelectInputValue');
        fireEvent.click(calculationSelect);
        fireEvent.click(
            screen.getByText('Net Revenue + Adjustments - Recoupment Cap')
        );
    };

    beforeEach(() => {
        jest.spyOn(
            getReferenceFlowthroughCalculationQuery,
            'useGetReferenceFlowthroughCalculations'
        ).mockReturnValue({
            data: {
                abacusReferenceFlowthroughCalculations: {
                    __typename: 'AbacusReferenceFlowthroughCalculationList',
                    totalCount:
                        referenceFlowthroughCalculations
                            ?.abacusReferenceFlowthroughCalculation
                            ?.totalCount || 0,
                    items: referenceFlowthroughCalculations.abacusReferenceFlowthroughCalculation.items.map(
                        item => ({
                            ...item,
                            __typename: 'AbacusReferenceFlowthroughCalculation',
                        })
                    ),
                },
            },
            loading: false,
            error: undefined,
        });
    });

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

    describe('Modal Render', () => {
        it('renders Contract Flowthrough fullscreen modal', () => {
            render();
            expect(screen.getByText('Add Flowthrough')).toBeInTheDocument();
            expect(screen.getByText('Set up Flowthrough')).toBeInTheDocument();
            expect(screen.getByText('Cancel')).toBeInTheDocument();
        });

        it('renders the fullscreen modal text', () => {
            render();
            const fullScreenModal = screen.getByTestId('FullscreenModal-fluid');
            expect(fullScreenModal).toBeDefined();
            expect(fullScreenModal).toHaveTextContent(
                'Set up the calculation for flowthrough for this contract.'
            );
            expect(fullScreenModal).toHaveTextContent(
                "You'll be able to edit it later"
            );
        });

        it('renders label for cancel button', () => {
            render();
            expect(screen.getByText('Cancel')).toBeInTheDocument();
        });

        it('closes fullscreen modal on cancel button click', async () => {
            const history = render();
            screen.getByText('Cancel').click();
            await waitFor(() => {
                expect(history.push).toHaveBeenCalled();
            });
        });
    });

    describe('Test Input Fields', () => {
        it('does not allow input of non-numeric characters in flowthrough rate field', () => {
            render();

            selectCalculationWithRate();

            const contractFlowthroughRateInput = screen.getByTestId(
                'contractFlowthroughRate'
            );
            fireEvent.change(contractFlowthroughRateInput, {
                target: { value: 'abc' },
            });
            fireEvent.blur(contractFlowthroughRateInput);
            expect(
                screen.getByText(CONTRACT_FLOWTHROUGH_RATE_ERROR_MSG)
            ).toBeInTheDocument();
        });

        it('does not allow input of non-numeric characters in recoupment cap field', () => {
            render();

            selectCalculationWithRecoupmentCap();

            const recoupmentCapInput = screen.getByTestId(
                'contractFlowthroughRecoupmentCap'
            );
            fireEvent.change(recoupmentCapInput, {
                target: { value: 'abc' },
            });
            fireEvent.blur(recoupmentCapInput);
            expect(
                screen.getByText(CONTRACT_FLOWTHROUGH_RECOUPMENT_CAP_ERROR_MSG)
            ).toBeInTheDocument();
        });

        it('clears error message when valid flowthrough rate is provided', () => {
            render();
            selectCalculationWithRate();
            const contractFlowthroughRateInput = screen.getByTestId(
                'contractFlowthroughRate'
            );
            fireEvent.change(contractFlowthroughRateInput, {
                target: { value: 'abc' },
            });
            fireEvent.blur(contractFlowthroughRateInput);
            expect(
                screen.getByText(CONTRACT_FLOWTHROUGH_RATE_ERROR_MSG)
            ).toBeInTheDocument();
            fireEvent.change(contractFlowthroughRateInput, {
                target: { value: '10' },
            });
            fireEvent.blur(contractFlowthroughRateInput);
            expect(
                screen.queryByText(CONTRACT_FLOWTHROUGH_RATE_ERROR_MSG)
            ).not.toBeInTheDocument();
        });

        it('clears error message when valid recoupment cap is provided', () => {
            render();
            selectCalculationWithRecoupmentCap();
            const recoupmentCapInput = screen.getByTestId(
                'contractFlowthroughRecoupmentCap'
            );
            fireEvent.change(recoupmentCapInput, {
                target: { value: 'abc' },
            });
            fireEvent.blur(recoupmentCapInput);
            expect(
                screen.getByText(CONTRACT_FLOWTHROUGH_RECOUPMENT_CAP_ERROR_MSG)
            ).toBeInTheDocument();
            fireEvent.change(recoupmentCapInput, {
                target: { value: '5000' },
            });
            fireEvent.blur(recoupmentCapInput);
            expect(
                screen.queryByText(
                    CONTRACT_FLOWTHROUGH_RECOUPMENT_CAP_ERROR_MSG
                )
            ).not.toBeInTheDocument();
        });
    });

    describe('Save Handler', () => {
        it('displays server error when save handler fails', async () => {
            const mockSaveHandler = jest
                .fn()
                .mockRejectedValue(new Error('Server save error'));
            jest.spyOn(
                createContractFlowthroughHook,
                'useSaveHandler'
            ).mockReturnValue(mockSaveHandler);

            render();

            selectCalculationWithRate();

            fireEvent.change(screen.getByTestId('contractFlowthroughRate'), {
                target: { value: '10' },
            });

            const saveButton = screen.getByTestId(
                'contractFlowthroughSaveButton'
            );
            expect(saveButton).toBeEnabled();

            fireEvent.click(saveButton);

            const alert = await screen.findByTestId('Alert');

            expect(alert).toHaveTextContent(
                'Error: Server save error, Failed to create contract flowthrough. Please contact the Abacus team'
            );
        });

        it('disables the save button while submission is in progress', async () => {
            let resolveHandler: () => void;
            const pendingPromise = new Promise<void>(
                resolve => (resolveHandler = resolve)
            );
            const mockSaveHandler = jest.fn().mockReturnValue(pendingPromise);
            jest.spyOn(
                createContractFlowthroughHook,
                'useSaveHandler'
            ).mockReturnValue(mockSaveHandler);

            render();
            selectCalculationWithRate();
            fireEvent.change(screen.getByTestId('contractFlowthroughRate'), {
                target: { value: '10' },
            });

            const saveButton = screen.getByTestId(
                'contractFlowthroughSaveButton'
            );
            expect(saveButton).toBeEnabled();

            fireEvent.click(saveButton);

            await waitFor(() => expect(saveButton).toBeDisabled());

            resolveHandler!();
            await waitFor(() => expect(mockSaveHandler).toHaveBeenCalled());
        });

        it('re-enables the save button after a submission error', async () => {
            const mockSaveHandler = jest
                .fn()
                .mockRejectedValue(new Error('Server save error'));
            jest.spyOn(
                createContractFlowthroughHook,
                'useSaveHandler'
            ).mockReturnValue(mockSaveHandler);

            render();
            selectCalculationWithRate();
            fireEvent.change(screen.getByTestId('contractFlowthroughRate'), {
                target: { value: '10' },
            });

            const saveButton = screen.getByTestId(
                'contractFlowthroughSaveButton'
            );
            fireEvent.click(saveButton);

            await waitFor(() => expect(saveButton).toBeEnabled());
        });

        it('calls the save handler with the correct payload when Save is clicked', async () => {
            const mockSaveHandler = jest.fn().mockResolvedValue({});
            jest.spyOn(
                createContractFlowthroughHook,
                'useSaveHandler'
            ).mockReturnValue(mockSaveHandler);

            render();

            selectCalculationWithRate();

            fireEvent.change(screen.getByTestId('contractFlowthroughRate'), {
                target: { value: '10' },
            });

            const saveButton = screen.getByTestId(
                'contractFlowthroughSaveButton'
            );
            expect(saveButton).toBeEnabled();

            fireEvent.click(saveButton);

            await waitFor(() => {
                expect(mockSaveHandler).toHaveBeenCalledWith({
                    flowthroughRate: '10',
                    recoupmentCap: undefined,
                    calculationComment: null,
                    hasAutomaticShutoff: true,
                    referenceFlowthroughCalculationId: '4',
                });
            });
            await waitFor(() => {
                expect(history.push).toHaveBeenCalled();
            });
        });
    });
});
