/**
 * 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 { ReactChild } from 'react';
import {
  cleanup,
  render,
  screen,
  userEvent,
  waitFor,
  within,
} from 'spec/helpers/testing-library';
import DatasourcePanel, {
  IDatasource,
  Props as DatasourcePanelProps,
} from 'src/explore/components/DatasourcePanel';
import {
  columns,
  metrics,
} from 'src/explore/components/DatasourcePanel/fixtures';
import { DatasourceType } from '@superset-ui/core';
import DatasourceControl from 'src/explore/components/controls/DatasourceControl';
import ExploreContainer from '../ExploreContainer';
import {
  DndColumnSelect,
  DndMetricSelect,
} from '../controls/DndColumnSelectControl';
import { FoldersEditorItemType } from 'src/components/Datasource/types';

jest.mock(
  'react-virtualized-auto-sizer',
  () =>
    ({ children }: { children: (params: { height: number }) => ReactChild }) =>
      children({ height: 500 }),
);

const datasource: IDatasource = {
  id: 1,
  type: DatasourceType.Table,
  columns,
  metrics,
  database: {
    id: 1,
  },
  datasource_name: 'table1',
};

const datasourceWithFolders: IDatasource = {
  ...datasource,
  folders: [
    {
      name: 'Test folder',
      type: FoldersEditorItemType.Folder,
      uuid: '1',
      children: [
        {
          name: 'Test nested folder',
          type: FoldersEditorItemType.Folder,
          uuid: '1.1',
          children: [
            {
              type: FoldersEditorItemType.Metric,
              uuid: metrics[0].uuid,
              name: metrics[0].metric_name,
            },
          ],
        },
      ],
    },
    {
      name: 'Second test folder',
      type: FoldersEditorItemType.Folder,
      uuid: '2',
      children: [
        {
          type: FoldersEditorItemType.Column,
          uuid: columns[0].uuid,
          name: columns[0].column_name,
        },
        {
          type: FoldersEditorItemType.Column,
          uuid: columns[1].uuid,
          name: columns[1].column_name,
        },
      ],
    },
  ],
};

const mockUser = {
  createdOn: '2021-04-27T18:12:38.952304',
  email: 'admin',
  firstName: 'admin',
  isActive: true,
  lastName: 'admin',
  permissions: {},
  roles: { Admin: Array(173) },
  userId: 1,
  username: 'admin',
  isAnonymous: false,
};

const props: DatasourcePanelProps = {
  datasource,
  controls: {
    datasource: {
      validationErrors: null,
      mapStateToProps: () => ({ value: undefined }),
      type: DatasourceControl,
      label: 'hello',
      datasource,
      user: mockUser,
    },
  },
  actions: {
    setControlValue: jest.fn(),
  },
  width: 300,
};

const propsWithFolders = {
  ...props,
  datasource: datasourceWithFolders,
  controls: {
    ...props.controls,
    datasource: {
      ...props.controls.datasource,
      datasource: datasourceWithFolders,
    },
  },
};

const metricProps = {
  savedMetrics: [],
  columns: [],
  onChange: jest.fn(),
};

const search = (value: string, input: HTMLElement) => {
  userEvent.clear(input);
  userEvent.type(input, value);
};

test('should render', async () => {
  const { container } = render(<DatasourcePanel {...props} />, {
    useRedux: true,
    useDnd: true,
  });
  expect(await screen.findByText(/metrics/i)).toBeInTheDocument();
  expect(container).toBeVisible();
});

test('should display items in controls', async () => {
  render(<DatasourcePanel {...props} />, { useRedux: true, useDnd: true });
  expect(await screen.findByText('Metrics')).toBeInTheDocument();
  expect(screen.getByText('Columns')).toBeInTheDocument();
});

test('should render the metrics', async () => {
  jest.setTimeout(10000);
  render(
    <ExploreContainer>
      <DatasourcePanel {...props} />
      <DndMetricSelect {...metricProps} />
    </ExploreContainer>,
    { useRedux: true, useDnd: true },
  );
  metrics.forEach(metric =>
    expect(screen.getByText(metric.metric_name)).toBeInTheDocument(),
  );
});

test('should render the columns', async () => {
  render(
    <ExploreContainer>
      <DatasourcePanel {...props} />
      <DndMetricSelect {...metricProps} />
    </ExploreContainer>,
    { useRedux: true, useDnd: true },
  );
  columns.forEach(col =>
    expect(screen.getByText(col.column_name)).toBeInTheDocument(),
  );
});

// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
describe('DatasourcePanel', () => {
  beforeAll(() => {
    jest.setTimeout(30000);
  });

  afterEach(async () => {
    cleanup();
    await new Promise(resolve => setTimeout(resolve, 0));
  });

  test('should search and render matching columns', async () => {
    const { unmount } = render(
      <ExploreContainer>
        <DatasourcePanel {...props} />
        <DndMetricSelect {...metricProps} />
      </ExploreContainer>,
      { useRedux: true, useDnd: true },
    );

    const searchInput = screen.getByPlaceholderText('Search Metrics & Columns');

    await waitFor(() => {
      expect(searchInput).toBeInTheDocument();
    });

    search(columns[0].column_name, searchInput);

    await waitFor(
      () => {
        expect(screen.getByText(columns[0].column_name)).toBeInTheDocument();
        expect(
          screen.queryByText(columns[1].column_name),
        ).not.toBeInTheDocument();
      },
      { timeout: 10000 },
    );

    unmount();
  }, 15000);
});

test('should search and render matching metrics', async () => {
  render(
    <ExploreContainer>
      <DatasourcePanel {...props} />
      <DndMetricSelect {...metricProps} />
    </ExploreContainer>,
    { useRedux: true, useDnd: true },
  );
  const searchInput = screen.getByPlaceholderText('Search Metrics & Columns');

  search(metrics[0].metric_name, searchInput);

  await waitFor(() => {
    expect(screen.getByText(metrics[0].metric_name)).toBeInTheDocument();
    expect(screen.queryByText(metrics[1].metric_name)).not.toBeInTheDocument();
  });
});

test('should render a warning', async () => {
  const deprecatedDatasource = {
    ...datasource,
    extra: JSON.stringify({ warning_markdown: 'This is a warning.' }),
  };
  const newProps = {
    ...props,
    datasource: deprecatedDatasource,
    controls: {
      datasource: {
        ...props.controls.datasource,
        datasource: deprecatedDatasource,
        user: mockUser,
      },
    },
  };
  render(<DatasourcePanel {...newProps} />, { useRedux: true, useDnd: true });
  expect(
    await screen.findByRole('img', { name: 'warning' }),
  ).toBeInTheDocument();
});

test('should render a create dataset infobox', async () => {
  const newProps = {
    ...props,
    datasource: {
      ...datasource,
      type: DatasourceType.Query,
    },
  };
  render(<DatasourcePanel {...newProps} />, { useRedux: true, useDnd: true });

  const createButton = await screen.findByRole('button', {
    name: /create a dataset/i,
  });
  const infoboxText = screen.getByText(/to edit or add columns and metrics./i);

  expect(createButton).toBeVisible();
  expect(infoboxText).toBeVisible();
});

test('should not render a save dataset modal when datasource is not query or dataset', async () => {
  const newProps = {
    ...props,
    datasource: {
      ...datasource,
      type: DatasourceType.Table,
    },
  };
  render(<DatasourcePanel {...newProps} />, { useRedux: true, useDnd: true });
  expect(await screen.findByText(/metrics/i)).toBeInTheDocument();

  expect(screen.queryByText(/create a dataset/i)).not.toBeInTheDocument();
});

test('should render only droppable metrics and columns', async () => {
  const column1FilterProps = {
    type: 'DndColumnSelect' as const,
    name: 'Filter',
    onChange: jest.fn(),
    options: [{ column_name: columns[1].column_name }],
    actions: { setControlValue: jest.fn() },
  };
  const column2FilterProps = {
    type: 'DndColumnSelect' as const,
    name: 'Filter',
    onChange: jest.fn(),
    options: [
      { column_name: columns[1].column_name },
      { column_name: columns[2].column_name },
    ],
    actions: { setControlValue: jest.fn() },
  };
  const { getByTestId, unmount } = render(
    <ExploreContainer>
      <DatasourcePanel {...props} />
      <DndColumnSelect {...column1FilterProps} />
      <DndColumnSelect {...column2FilterProps} />
    </ExploreContainer>,
    { useRedux: true, useDnd: true },
  );

  await waitFor(
    () => {
      const selections = getByTestId('fieldSelections');
      expect(
        within(selections).queryByText(columns[0].column_name),
      ).not.toBeInTheDocument();
      expect(
        within(selections).getByText(columns[1].column_name),
      ).toBeInTheDocument();
      expect(
        within(selections).getByText(columns[2].column_name),
      ).toBeInTheDocument();
    },
    { timeout: 10000 },
  );

  unmount();
});

test('Renders with custom folders', () => {
  render(
    <ExploreContainer>
      <DatasourcePanel {...propsWithFolders} />
      <DndMetricSelect {...metricProps} />
    </ExploreContainer>,
    {
      useRedux: true,
      useDnd: true,
    },
  );

  expect(screen.getByText('Test folder')).toBeInTheDocument();
  expect(screen.getByText('Test nested folder')).toBeInTheDocument();
  expect(screen.getByText('Second test folder')).toBeInTheDocument();
  expect(screen.getByText('Metrics')).toBeInTheDocument();
  expect(screen.getByText('Columns')).toBeInTheDocument();

  columns.forEach(col => {
    expect(screen.getByText(col.column_name)).toBeInTheDocument();
  });

  metrics.forEach(metric => {
    expect(screen.getByText(metric.metric_name)).toBeInTheDocument();
  });

  expect(screen.getAllByTestId('DatasourcePanelDragOption').length).toEqual(5);
  expect(screen.getAllByTestId('datasource-panel-divider').length).toEqual(3);
});

test('Collapse folders', () => {
  render(
    <ExploreContainer>
      <DatasourcePanel {...propsWithFolders} />
      <DndMetricSelect {...metricProps} />
    </ExploreContainer>,
    {
      useRedux: true,
      useDnd: true,
    },
  );

  userEvent.click(screen.getByText('Test folder'));

  expect(screen.getByText('Test folder')).toBeInTheDocument();
  expect(screen.queryByText('Test nested folder')).not.toBeInTheDocument();
  expect(screen.getByText('Second test folder')).toBeInTheDocument();
  expect(screen.getByText('Metrics')).toBeInTheDocument();
  expect(screen.getByText('Columns')).toBeInTheDocument();

  expect(screen.queryByText(metrics[0].metric_name)).not.toBeInTheDocument();

  userEvent.click(screen.getByText('Test folder'));

  expect(screen.getByText('Test folder')).toBeInTheDocument();
  expect(screen.getByText('Test nested folder')).toBeInTheDocument();
  expect(screen.getByText('Second test folder')).toBeInTheDocument();
  expect(screen.getByText('Metrics')).toBeInTheDocument();
  expect(screen.getByText('Columns')).toBeInTheDocument();

  expect(screen.getByText(metrics[0].metric_name)).toBeInTheDocument();
});

test('Default Metrics and Columns folders dont render when all metrics and columns are assigned to custom folders', () => {
  const datasourceWithFullFolders: IDatasource = {
    ...datasource,
    folders: [
      {
        name: 'Test folder',
        type: FoldersEditorItemType.Folder,
        uuid: '1',
        children: [
          {
            name: 'Test nested folder',
            type: FoldersEditorItemType.Folder,
            uuid: '1.1',
            children: metrics.map(m => ({
              type: FoldersEditorItemType.Metric,
              uuid: m.uuid,
              name: m.metric_name,
            })),
          },
        ],
      },
      {
        name: 'Second test folder',
        type: FoldersEditorItemType.Folder,
        uuid: '2',
        children: columns.map(c => ({
          type: FoldersEditorItemType.Column,
          uuid: c.uuid,
          name: c.column_name,
        })),
      },
    ],
  };
  const propsWithFullFolders = {
    ...props,
    datasource: datasourceWithFullFolders,
    controls: {
      ...props.controls,
      datasource: {
        ...props.controls.datasource,
        datasource: datasourceWithFullFolders,
      },
    },
  };
  render(
    <ExploreContainer>
      <DatasourcePanel {...propsWithFullFolders} />
      <DndMetricSelect {...metricProps} />
    </ExploreContainer>,
    {
      useRedux: true,
      useDnd: true,
    },
  );

  expect(screen.getByText('Test folder')).toBeInTheDocument();
  expect(screen.getByText('Test nested folder')).toBeInTheDocument();
  expect(screen.getByText('Second test folder')).toBeInTheDocument();
  expect(screen.queryByText('Metrics')).not.toBeInTheDocument();
  expect(screen.queryByText('Columns')).not.toBeInTheDocument();

  columns.forEach(col => {
    expect(screen.getByText(col.column_name)).toBeInTheDocument();
  });

  metrics.forEach(metric => {
    expect(screen.getByText(metric.metric_name)).toBeInTheDocument();
  });

  expect(screen.getAllByTestId('DatasourcePanelDragOption').length).toEqual(5);
  expect(screen.getAllByTestId('datasource-panel-divider').length).toEqual(1);
});
