import React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import { SelectInstance } from 'react-select';
import AsyncSelect from 'react-select/async';
import AsyncCreatable from 'react-select/async-creatable';
import AsyncDropdown, { DropdownOption, Props } from '../asyncDropdown';

describe('<AsyncDropdown>', () => {
    const value = [
        { name: 'Artist 1', id: 1 },
        { name: 'Artist 2', id: 2 },
    ];
    const mappedValue = [
        { data: value[0], label: value[0].name, value: value[0].name },
        { data: value[1], label: value[1].name, value: value[1].name },
    ];
    const renderComponent = (props?: Props) => shallow(<AsyncDropdown value={[]} {...props} />);
    const getInstance = (component: ShallowWrapper) => component.instance() as AsyncDropdown;

    describe('when it renders', () => {
        it('displays correct Creatable layout', () => {
            expect(renderComponent().find(AsyncCreatable)).toMatchSnapshot();
        });

        it('displays correct Select layout', () => {
            const testProps = { isCreatable: false, value: [] };
            expect(renderComponent(testProps).find(AsyncSelect)).toMatchSnapshot();
        });

        it('has truthy isClearable value if not MultiSelect', () => {
            const testProps = { multi: false, value: [] };
            const asyncCreatable = renderComponent(testProps).find(AsyncCreatable);
            expect(asyncCreatable.prop('isClearable')).toBeTruthy();
        });

        it('has correct mapped value', () => {
            const testProps = { value };
            expect(renderComponent(testProps).find(AsyncCreatable)).toHaveProp(
                'value',
                mappedValue
            );
        });

        it('returns null by default for noOptionsMessage property', () => {
            const component = renderComponent().find(AsyncCreatable);
            const func = component.prop('noOptionsMessage');
            expect(func).toBeDefined();
            expect(func?.({ inputValue: 'hi' })).toBeNull();
        });

        it('has correct Create label is formatter is set', () => {
            const testProps = {
                value,
                formatCreateLabel: (inputValue: string) => `Create ${inputValue}`,
            };
            const instance = getInstance(renderComponent(testProps));
            expect(instance.getFormatCreateLabel('Test')).toMatchSnapshot();
        });

        it('has correct Create label is formatter is not set', () => {
            const testProps = { value, formatCreateLabel: undefined };
            const instance = getInstance(renderComponent(testProps));
            expect(instance.getFormatCreateLabel('Test')).toBeUndefined();
        });
    });

    describe('with handlers', () => {
        let selectRef: SelectInstance<DropdownOption>;

        beforeEach(() => {
            selectRef = {
                onMenuOpen: jest.fn(),
                handleInputChange: jest.fn(),
            } as unknown as SelectInstance<DropdownOption>;
        });

        it('calls setRef and sets correct value', () => {
            const instance = getInstance(renderComponent());
            instance.setRef(selectRef);
            expect(instance.select).toBe(selectRef);
        });
        it('calls handleOnInputChange and sets correct state', () => {
            const component = renderComponent();
            getInstance(component).handleOnInputChange('Test', {
                action: 'set-value',
            });
            expect(component).toHaveState('inputValue', 'Test');
        });
        it('calls handleOnInputChange and does not change state', () => {
            const component = renderComponent();
            component.setState({ inputValue: 'Existing value' });
            getInstance(component).handleOnInputChange('New value', {
                action: 'input-blur',
            });
            expect(component).toHaveState('inputValue', 'Existing value');
        });
        it('calls handleOnInputChange and calls onInputChange if value changed', () => {
            const testProps = { onInputChange: jest.fn(), value: [] };
            const component = renderComponent(testProps);
            getInstance(component).handleOnInputChange('Test', {
                action: 'input-change',
            });
            expect(testProps.onInputChange).toHaveBeenCalledWith('Test');
        });
        it('calls handleOnFocus and triggers data auto fetching', () => {
            const component = renderComponent();
            const instance = getInstance(component);
            component.setState({ inputValue: 'Test' });
            instance.setRef(selectRef);
            instance.handleOnFocus();
            expect(selectRef.onMenuOpen).toHaveBeenCalled();
            expect(selectRef.handleInputChange).toHaveBeenCalledWith({
                currentTarget: { value: 'Test' },
            });
        });
        it('calls handleOnFocus and omits data fetching', () => {
            const component = renderComponent();
            const instance = getInstance(component);
            component.setState({ inputValue: 'Test' });
            instance.setRef(null as unknown as SelectInstance<DropdownOption>);
            instance.handleOnFocus();
            expect(selectRef.onMenuOpen).toHaveBeenCalledTimes(0);
        });
        it('calls handleOnCreateOption and triggers onCreate', () => {
            const testProps = { onCreate: jest.fn(), value: [] };
            const instance = getInstance(renderComponent(testProps));
            instance.handleOnCreateOption('Test');
            expect(testProps.onCreate).toHaveBeenCalledWith('Test');
        });
        it('calls handleOnChange and triggers onChange', () => {
            const testProps = { onChange: jest.fn(), value: [] };
            const instance = getInstance(renderComponent(testProps));
            const option = {
                value: 'Test',
                label: 'Test',
                data: { name: 'Test' },
            };
            instance.handleOnChange(option);
            expect(testProps.onChange).toHaveBeenCalledWith([option]);
        });

        // eslint-disable-next-line jest/no-done-callback
        it('calls handleLoadOptions and fetches data successfully', (done) => {
            jest.useFakeTimers();
            const onFetch = jest.fn();
            onFetch.mockReturnValue(Promise.resolve(value));

            const testProps = { onFetch, searchWait: 0, value: [] };
            const instance = getInstance(renderComponent(testProps));
            const callback = (callbackValue: DropdownOption[]) => {
                expect(onFetch).toHaveBeenCalledWith('Test');
                expect(callbackValue).toEqual(mappedValue);
                done();
            };
            instance.handleLoadOptions('Test', callback);
            jest.advanceTimersByTime(100);
        });
    });
});
