import React from 'react';
import { fireEvent, render } from '@testing-library/react';
import { MapboxSearchBox, MapboxHTMLEvent, Theme } from '@mapbox/search-js-web';
import { SearchBoxOptions, SearchSession } from '@mapbox/search-js-core';

import mapboxgl from 'mapbox-gl';

import { SearchBox } from '../../src';
import { SearchBoxRefType } from '../../src/components/SearchBox';

const OPTIONS: Partial<SearchBoxOptions> = { language: 'de' };
const THEME: Theme = { variables: { fontFamily: 'Comic Sans MS ' } };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const MAP = {} as any;

const suggestFuncMock = jest.fn();
jest
  .spyOn(SearchSession.prototype, 'suggest')
  .mockImplementation(suggestFuncMock);

const unmockedFetch = global.fetch;

beforeAll(() => {
  /* eslint-disable-next-line */
  // @ts-ignore
  global.fetch = () => {
    Promise.resolve({
      json: () => Promise.resolve({ test: 'value' })
    });
  };
});

afterAll(() => {
  global.fetch = unmockedFetch;
});

describe('SearchBox', () => {
  it('renders', () => {
    const handleChange = jest.fn();
    const handleSuggest = jest.fn();
    const handleSuggestError = jest.fn();
    const handleRetrieve = jest.fn();
    const handleClear = jest.fn();
    const handleInterceptSearch = jest.fn();

    const { baseElement, rerender, unmount } = render(
      <SearchBox
        accessToken="xyz"
        options={OPTIONS}
        theme={THEME}
        placeholder="Search for an address"
        marker={{ color: 'orange' }}
        mapboxgl={mapboxgl}
        onChange={handleChange}
        onSuggest={handleSuggest}
        onSuggestError={handleSuggestError}
        onRetrieve={handleRetrieve}
        onClear={handleClear}
        interceptSearch={handleInterceptSearch}
      />
    );

    const search =
      baseElement.querySelector<MapboxSearchBox>('mapbox-search-box');
    expect(search).toBeTruthy();

    expect(search.value).toBe('');

    expect(search.accessToken).toBe('xyz');
    expect(search.options).toEqual(OPTIONS);
    expect(search.theme).toEqual(THEME);
    expect(search.placeholder).toBe('Search for an address');
    expect(search.marker).toEqual({ color: 'orange' });
    expect(search.mapboxgl).toBe(mapboxgl);

    search.dispatchEvent(new MapboxHTMLEvent('input', 'foo'));
    expect(handleChange).toHaveBeenCalledTimes(1);

    // test interception that mutates search text
    handleInterceptSearch.mockImplementation((val) => val + ' main');
    fireEvent.input(search.input, { target: { value: '123' } });
    expect(handleInterceptSearch).toHaveBeenCalledTimes(1);
    expect(suggestFuncMock).toHaveBeenCalledWith('123 main', {
      language: 'de'
    });

    handleInterceptSearch.mockClear();
    suggestFuncMock.mockClear();

    // test interception that not sending the request returning `undefined` value
    handleInterceptSearch.mockImplementation(() => undefined);
    fireEvent.input(search.input, { target: { value: '123' } });
    expect(handleInterceptSearch).toHaveBeenCalledTimes(1);
    expect(suggestFuncMock).toHaveBeenCalledTimes(0);

    search.dispatchEvent(new MapboxHTMLEvent('suggest', { suggestions: [] }));
    expect(handleSuggest).toHaveBeenCalledTimes(1);

    search.dispatchEvent(new MapboxHTMLEvent('suggesterror', new Error('foo')));
    expect(handleSuggestError).toHaveBeenCalledTimes(1);

    search.dispatchEvent(
      new MapboxHTMLEvent('retrieve', {
        suggestions: []
      })
    );
    expect(handleRetrieve).toHaveBeenCalledTimes(1);

    search.dispatchEvent(new MapboxHTMLEvent('clear'));
    expect(handleClear).toHaveBeenCalledTimes(1);

    const bindMapFunc = jest.spyOn(search, 'bindMap');
    // eslint-disable-next-line @typescript-eslint/no-empty-function
    bindMapFunc.mockImplementation(() => {});

    const ref = React.createRef<SearchBoxRefType>();
    rerender(
      <SearchBox
        ref={ref}
        accessToken="xyz"
        options={OPTIONS}
        theme={THEME}
        value="hello"
        map={MAP}
      />
    );

    expect(search.bindMap).toHaveBeenCalled();
    expect(search.bindMap).toHaveBeenCalledWith(MAP);

    expect(ref.current).toBeTruthy();
    jest.spyOn(search, 'focus');
    ref.current.focus();
    expect(search.focus).toHaveBeenCalledTimes(1);

    jest.spyOn(search, 'search');
    ref.current.search('123 main st');
    expect(search.search).toHaveBeenCalledWith('123 main st');

    expect(search.marker).toBeFalsy();
    expect(search.mapboxgl).toBeUndefined();

    const oldRef = ref.current;
    unmount();
    expect(oldRef.focus).toThrowError('SearchBox is not mounted');
    expect(oldRef.search).toThrowError('SearchBox is not mounted');
  });
});
