import React from 'react';
import { fireEvent, screen, waitFor } from '@testing-library/react';
import { renderInAppContext } from '@theorchard/suite-testing';
import mockMinimums from 'src/__fixtures__/graphql/payment-minimums-response.json';
import * as paymentMinimumsMutations from 'src/apollo/mutations/payment-minimums';
import * as paymentMinimumsQuery from 'src/apollo/queries/payment-minimums';
import PaymentMinimumsListAndEdit from 'src/components/payment-minimums/payment-minimums-list-and-edit';
import type { FormattedPaymentMinimum } from 'src/types/payment-minimums';

describe('<PaymentMinimumsListAndEdit />', () => {
    const mockResponse: Record<
        string,
        FormattedPaymentMinimum & { id: string }
    > = {};

    const render = () => renderInAppContext(<PaymentMinimumsListAndEdit />);
    let getRequestSpy: jest.SpyInstance;
    let putRequestSpy: jest.SpyInstance;

    mockMinimums.abacusPaymentMinimums.items.forEach(min => {
        mockResponse[min.paymentMinimumId] = {
            ...min,
            id: min.paymentMinimumId,
        };
    });

    const updatePaymentMinimums = jest.fn().mockResolvedValue({});

    afterEach(jest.restoreAllMocks);
    beforeEach(() => {
        getRequestSpy = jest
            .spyOn(paymentMinimumsQuery, 'usePaymentMinimums')
            .mockReturnValue({
                data: mockMinimums,
                error: false,
                loading: false,
            });
        putRequestSpy = jest
            .spyOn(paymentMinimumsMutations, 'useUpdatePaymentMinimums')
            .mockReturnValue(updatePaymentMinimums);
    });

    it('requests payment minimums on render', () => {
        render();
        expect(getRequestSpy).toHaveBeenCalled();
    });

    it('renders input fields', () => {
        render();
        const firstMin = mockResponse[1];
        expect(screen.getAllByTestId('amount-input')[0].value).toEqual(
            `$${firstMin.checkAmount}`
        );
    });

    it('renders in view mode', () => {
        render();
        const inputs = screen.getAllByTestId('amount-input');

        inputs.forEach(input => {
            expect(input.disabled).toBeTruthy();
        });

        expect(screen.queryByText('Cancel')).toBeNull();
        expect(screen.queryByText('Save')).toBeNull();
    });

    it('switches to edit mode', () => {
        render();
        const editButton = screen.getByText('Edit');

        fireEvent.click(editButton);
        const inputs = screen.getAllByTestId('amount-input');

        inputs.forEach(input => {
            expect(input.disabled).toBeFalsy();
        });

        expect(screen.getByText('Cancel')).toBeTruthy();
        expect(screen.getByText('Save')).toBeTruthy();
    });

    it('highlights an input when the value is changed', () => {
        render();

        const input = screen.getAllByTestId('amount-input')[0];
        fireEvent.change(input, { target: { value: '$100.00' } });

        expect(input.classList).toContain('updated');
    });

    it('raises an error when an input value is blank', () => {
        render();

        const input = screen.getAllByTestId('amount-input')[0];
        fireEvent.change(input, { target: { value: '' } });

        expect(
            screen.getAllByText('Minimum cannot be blank').length
        ).toBeGreaterThan(0);
    });

    it('resets inputs when the cancel button is clicked', async () => {
        render();
        const editButton = screen.getByText('Edit');
        fireEvent.click(editButton);

        let inputs = await screen.findAllByTestId('amount-input');
        let firstFour = Object.values(inputs).slice(0, 4);

        firstFour.forEach(input =>
            fireEvent.change(input, { target: { value: '$50.00' } })
        );

        const updatedInputs = document.querySelectorAll('input.updated');
        expect(Object.values(updatedInputs).length).toEqual(firstFour.length);

        const cancelBtn = screen.getAllByText('Cancel')[0];
        fireEvent.click(cancelBtn);

        inputs = await screen.findAllByTestId('amount-input');
        firstFour = Object.values(inputs).slice(0, 4);

        firstFour.forEach(input => {
            expect(input.value).not.toEqual('$50.00');
            expect(input.classList.contains('updated')).toBe(false);
        });

        expect(screen.queryByText('Cancel')).toBeNull();
        expect(screen.queryByText('Save')).toBeNull();
    });

    it('sends a put request with all updated minimums when save is clicked', async () => {
        render();
        const editButton = screen.getByText('Edit');
        fireEvent.click(editButton);

        const inputs = await screen.findAllByTestId('amount-input');
        const firstFour = Object.values(inputs).slice(0, 4);

        firstFour.forEach(input => {
            fireEvent.change(input, { target: { value: '$50.00' } });
        });

        fireEvent.click(screen.getByText('Save'));

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