import React from 'react';
import { screen, fireEvent, waitFor } from '@testing-library/react';
import { renderInAppContext } from '@theorchard/suite-testing';
import {
    mockUseVendorById,
    mockUseVendorSearch,
} from 'src/__fixtures__/graphql/contract-term-hooks';
import {
    mockVendor,
    mockAnotherVendor,
    mockVendorSearchResults,
} from 'src/__fixtures__/graphql/contract-term-vendor';
import { VendorSearch } from 'src/components/contract-terms-refactored/contract-terms-distro-vendor-search';
import type { AbacusContractTermsInputErrors } from 'src/types/abacus-contract-terms-distro-input-errors';

describe('<VendorSearch />', () => {
    const mockOnChange = jest.fn();
    const mockSetErrors = jest.fn();

    let getVendorByIdMock: jest.Mock;
    let vendorSearchMock: jest.Mock;

    const render = (
        initialVendorId?: number,
        errors: AbacusContractTermsInputErrors = {}
    ) =>
        renderInAppContext(
            <VendorSearch
                onChange={mockOnChange}
                initialVendorId={initialVendorId}
                errors={errors}
                setErrors={mockSetErrors}
            />
        );

    beforeEach(() => {
        jest.clearAllMocks();
        getVendorByIdMock = mockUseVendorById();
        vendorSearchMock = mockUseVendorSearch();
    });

    it('renders label and input', () => {
        render();
        expect(screen.getByText('Account')).toBeInTheDocument();
        expect(screen.getByText('(Optional)')).toBeInTheDocument();
        expect(screen.getByText('Search for Account')).toBeInTheDocument();
    });

    it('calls getVendorById if initialVendorId is provided', () => {
        render(42);
        expect(getVendorByIdMock).toHaveBeenCalledTimes(1);
        expect(getVendorByIdMock).toHaveBeenCalledWith(42);
    });

    it('sets selected value and calls onChange when initial fetch is successful', async () => {
        getVendorByIdMock.mockResolvedValue({
            data: {
                orchardLabel: { __typename: 'OrchardLabel', ...mockVendor },
            },
        });
        render(mockVendor.id.vendorId);
        expect(
            await screen.findByDisplayValue(mockVendor.name)
        ).toBeInTheDocument();
        await waitFor(() => {
            expect(mockOnChange).toHaveBeenCalledWith(mockVendor);
        });
    });

    it('performs search and calls onChange on option select', async () => {
        vendorSearchMock.mockResolvedValue(mockVendorSearchResults);
        render();
        fireEvent.click(screen.getByTestId('SuiteSelectInput'));
        const searchInput = await screen.findByTestId(
            'SuiteListViewFilterInput'
        );
        fireEvent.change(searchInput, {
            target: { value: 'Another Vendor' },
        });
        const option = await screen.findByText(mockAnotherVendor.name);
        fireEvent.click(option);
        await waitFor(() =>
            expect(mockOnChange).toHaveBeenCalledWith(mockAnotherVendor)
        );
    });

    it('displays error message when vendor error is passed in props', () => {
        const errorText = 'Vendor search failed';
        render(undefined, { vendor: errorText });
        expect(screen.getByText(errorText)).toBeInTheDocument();
    });

    it('calls setErrors when fetching the initial vendor fails', async () => {
        const error = new Error('Network failed');
        getVendorByIdMock.mockRejectedValue(error);
        render(99);
        await waitFor(() => {
            expect(mockSetErrors).toHaveBeenCalledTimes(1);
        });
        await waitFor(() => {
            expect(mockSetErrors).toHaveBeenCalledWith(expect.any(Function));
        });
    });

    it('calls setErrors when the vendor search fails', async () => {
        const searchError = new Error('API is down');
        vendorSearchMock.mockRejectedValue(searchError);
        render();
        fireEvent.click(screen.getByTestId('SuiteSelectInput'));
        const searchInput = await screen.findByTestId(
            'SuiteListViewFilterInput'
        );
        fireEvent.change(searchInput, { target: { value: 'anything' } });
        await waitFor(() => {
            expect(mockSetErrors).toHaveBeenCalledTimes(1);
        });
        const setStateFunction = mockSetErrors.mock.calls[0][0];
        const previousState = { vendor: null };
        const newState = setStateFunction(previousState);
        expect(newState.vendor).toBe(undefined);
    });
});
