/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
import React from 'react';
import configureStore from 'redux-mock-store';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import sinon from 'sinon';
import fetchMock from 'fetch-mock';
import thunk from 'redux-thunk';
import { supersetTheme, ThemeProvider } from '@superset-ui/core';

import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';

import DatabaseSelector from 'src/components/DatabaseSelector';
import TableSelector from 'src/components/TableSelector';
import { initialState, tables } from 'spec/javascripts/sqllab/fixtures';

const mockStore = configureStore([thunk]);
const store = mockStore(initialState);

const FETCH_SCHEMAS_ENDPOINT = 'glob:*/api/v1/database/*/schemas/*';
const GET_TABLE_ENDPOINT = 'glob:*/superset/tables/1/*/*';
const GET_TABLE_NAMES_ENDPOINT = 'glob:*/superset/tables/1/main/*';

const mockedProps = {
  clearable: false,
  database: { id: 1, database_name: 'main' },
  dbId: 1,
  formMode: false,
  getDbList: sinon.stub(),
  handleError: sinon.stub(),
  horizontal: false,
  onChange: sinon.stub(),
  onDbChange: sinon.stub(),
  onSchemaChange: sinon.stub(),
  onTableChange: sinon.stub(),
  sqlLabMode: true,
  tableName: '',
  tableNameSticky: true,
};

const schemaOptions = {
  result: ['main', 'erf', 'superset'],
};
const selectedSchema = { label: 'main', title: 'main', value: 'main' };
const selectedTable = {
  extra: null,
  label: 'birth_names',
  schema: 'main',
  title: 'birth_names',
  type: undefined,
  value: 'birth_names',
};

async function mountAndWait(props = mockedProps) {
  const mounted = mount(<TableSelector {...props} />, {
    context: { store },
    wrappingComponent: ThemeProvider,
    wrappingComponentProps: { theme: supersetTheme },
  });
  await waitForComponentToPaint(mounted);

  return mounted;
}

describe('TableSelector', () => {
  let wrapper;

  beforeEach(async () => {
    fetchMock.reset();
    wrapper = await mountAndWait();
  });

  it('renders', () => {
    expect(wrapper.find(TableSelector)).toExist();
    expect(wrapper.find(DatabaseSelector)).toExist();
  });

  describe('change database', () => {
    afterEach(fetchMock.resetHistory);
    afterAll(fetchMock.reset);

    it('should fetch schemas', async () => {
      fetchMock.get(FETCH_SCHEMAS_ENDPOINT, { overwriteRoutes: true });
      act(() => {
        wrapper.find('[data-test="select-database"]').first().props().onChange({
          id: 1,
          database_name: 'main',
        });
      });
      await waitForComponentToPaint(wrapper);
      expect(fetchMock.calls(FETCH_SCHEMAS_ENDPOINT)).toHaveLength(1);
    });

    it('should fetch schema options', async () => {
      fetchMock.get(FETCH_SCHEMAS_ENDPOINT, schemaOptions, {
        overwriteRoutes: true,
      });
      act(() => {
        wrapper.find('[data-test="select-database"]').first().props().onChange({
          id: 1,
          database_name: 'main',
        });
      });
      await waitForComponentToPaint(wrapper);
      wrapper.update();
      expect(fetchMock.calls(FETCH_SCHEMAS_ENDPOINT)).toHaveLength(1);

      expect(
        wrapper.find('[name="select-schema"]').first().props().options,
      ).toEqual([
        { value: 'main', label: 'main', title: 'main' },
        { value: 'erf', label: 'erf', title: 'erf' },
        { value: 'superset', label: 'superset', title: 'superset' },
      ]);
    });

    it('should clear table options', async () => {
      act(() => {
        wrapper.find('[data-test="select-database"]').first().props().onChange({
          id: 1,
          database_name: 'main',
        });
      });
      await waitForComponentToPaint(wrapper);
      const props = wrapper.find('[name="async-select-table"]').first().props();
      expect(props.isDisabled).toBe(true);
      expect(props.value).toEqual(undefined);
    });
  });

  describe('change schema', () => {
    beforeEach(async () => {
      fetchMock.get(FETCH_SCHEMAS_ENDPOINT, schemaOptions, {
        overwriteRoutes: true,
      });
    });

    afterEach(fetchMock.resetHistory);
    afterAll(fetchMock.reset);

    it('should fetch table', async () => {
      fetchMock.get(GET_TABLE_NAMES_ENDPOINT, { overwriteRoutes: true });
      act(() => {
        wrapper.find('[data-test="select-database"]').first().props().onChange({
          id: 1,
          database_name: 'main',
        });
      });
      await waitForComponentToPaint(wrapper);
      act(() => {
        wrapper
          .find('[name="select-schema"]')
          .first()
          .props()
          .onChange(selectedSchema);
      });
      await waitForComponentToPaint(wrapper);
      expect(fetchMock.calls(GET_TABLE_NAMES_ENDPOINT)).toHaveLength(1);
    });

    it('should fetch table options', async () => {
      fetchMock.get(GET_TABLE_NAMES_ENDPOINT, tables, {
        overwriteRoutes: true,
      });
      act(() => {
        wrapper.find('[data-test="select-database"]').first().props().onChange({
          id: 1,
          database_name: 'main',
        });
      });
      await waitForComponentToPaint(wrapper);
      act(() => {
        wrapper
          .find('[name="select-schema"]')
          .first()
          .props()
          .onChange(selectedSchema);
      });
      await waitForComponentToPaint(wrapper);
      expect(
        wrapper.find('[name="select-schema"]').first().props().value[0],
      ).toEqual(selectedSchema);
      expect(fetchMock.calls(GET_TABLE_NAMES_ENDPOINT)).toHaveLength(1);
      const { options } = wrapper.find('[name="select-table"]').first().props();
      expect({ options }).toEqual(tables);
    });
  });

  describe('change table', () => {
    beforeEach(async () => {
      fetchMock.get(GET_TABLE_NAMES_ENDPOINT, tables, {
        overwriteRoutes: true,
      });
    });

    it('should change table value', async () => {
      act(() => {
        wrapper
          .find('[name="select-schema"]')
          .first()
          .props()
          .onChange(selectedSchema);
      });
      await waitForComponentToPaint(wrapper);
      act(() => {
        wrapper
          .find('[name="select-table"]')
          .first()
          .props()
          .onChange(selectedTable);
      });
      await waitForComponentToPaint(wrapper);
      expect(
        wrapper.find('[name="select-table"]').first().props().value,
      ).toEqual('birth_names');
    });

    it('should call onTableChange with schema from table object', async () => {
      act(() => {
        wrapper
          .find('[name="select-schema"]')
          .first()
          .props()
          .onChange(selectedSchema);
      });
      await waitForComponentToPaint(wrapper);
      act(() => {
        wrapper
          .find('[name="select-table"]')
          .first()
          .props()
          .onChange(selectedTable);
      });
      await waitForComponentToPaint(wrapper);
      expect(mockedProps.onTableChange.getCall(0).args[0]).toBe('birth_names');
      expect(mockedProps.onTableChange.getCall(0).args[1]).toBe('main');
    });
  });

  describe('getTableNamesBySubStr', () => {
    afterEach(fetchMock.resetHistory);
    afterAll(fetchMock.reset);

    it('should handle empty', async () => {
      act(() => {
        wrapper
          .find('[name="async-select-table"]')
          .first()
          .props()
          .loadOptions();
      });
      await waitForComponentToPaint(wrapper);
      const props = wrapper.find('[name="async-select-table"]').first().props();
      expect(props.isDisabled).toBe(true);
      expect(props.value).toEqual('');
    });

    it('should handle table name', async () => {
      wrapper.setProps({ schema: 'main' });
      fetchMock.get(GET_TABLE_ENDPOINT, tables, {
        overwriteRoutes: true,
      });
      act(() => {
        wrapper
          .find('[name="async-select-table"]')
          .first()
          .props()
          .loadOptions();
      });
      await waitForComponentToPaint(wrapper);
      expect(fetchMock.calls(GET_TABLE_ENDPOINT)).toHaveLength(1);
    });
  });
});
