/**
 * 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 { render, screen, fireEvent } from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
import { waitFor } from '@testing-library/react';

import ControlPopover, { PopoverProps } from './ControlPopover';

const createProps = (): Partial<PopoverProps> => ({
  trigger: 'click',
  title: 'Control Popover Test',
  content: <span data-test="control-popover-content">Information</span>,
});

const TestComponent: globalThis.React.FC<PopoverProps> = props => (
  <div id="controlSections">
    <div data-test="outer-container">
      <ControlPopover {...props}>
        <span data-test="control-popover">Click me</span>
      </ControlPopover>
    </div>
  </div>
);

const setupTest = (props: Partial<PopoverProps> = createProps()) => {
  const setStateMock = jest.fn();
  jest
    .spyOn(global.React, 'useState')
    .mockImplementation(((state: any) => [
      state,
      state === 'right' ? setStateMock : jest.fn(),
    ]) as any);

  const { container, rerender } = render(<TestComponent {...props} />);

  return {
    props,
    container,
    rerender,
    setStateMock,
  };
};

afterEach(() => {
  jest.restoreAllMocks();
});

test('Should render', () => {
  setupTest();
  expect(screen.getByTestId('control-popover')).toBeInTheDocument();
  userEvent.click(screen.getByTestId('control-popover'));
  expect(screen.getByText('Control Popover Test')).toBeInTheDocument();
  expect(screen.getByTestId('control-popover-content')).toBeInTheDocument();
});

test('Should lock the vertical scroll when the popover is visible', () => {
  setupTest();
  expect(screen.getByTestId('control-popover')).toBeInTheDocument();
  expect(screen.getByTestId('outer-container')).not.toHaveStyle(
    'overflowY: hidden',
  );
  userEvent.click(screen.getByTestId('control-popover'));
  expect(screen.getByTestId('outer-container')).toHaveStyle(
    'overflowY: hidden',
  );
  userEvent.click(document.body);
  expect(screen.getByTestId('outer-container')).not.toHaveStyle(
    'overflowY: hidden',
  );
});

test('Should place popover at the top', async () => {
  const { setStateMock } = setupTest({
    ...createProps(),
    getVisibilityRatio: () => ({ yRatio: 0.2, xRatio: 0.3 }),
  });

  expect(screen.getByTestId('control-popover')).toBeInTheDocument();
  userEvent.click(screen.getByTestId('control-popover'));

  await waitFor(() => {
    expect(setStateMock).toHaveBeenCalledWith('rightTop');
  });
});

test('Should place popover at the center', async () => {
  const { setStateMock } = setupTest({
    ...createProps(),
    getVisibilityRatio: () => ({ yRatio: 0.5, xRatio: 0.7 }),
  });

  expect(screen.getByTestId('control-popover')).toBeInTheDocument();
  userEvent.click(screen.getByTestId('control-popover'));

  await waitFor(() => {
    expect(setStateMock).toHaveBeenCalledWith('left');
  });
});

test('Should place popover at the bottom', async () => {
  const { setStateMock } = setupTest({
    ...createProps(),
    getVisibilityRatio: () => ({ yRatio: 0.9, xRatio: 0.2 }),
  });

  expect(screen.getByTestId('control-popover')).toBeInTheDocument();
  userEvent.click(screen.getByTestId('control-popover'));

  await waitFor(() => {
    expect(setStateMock).toHaveBeenCalledWith('rightBottom');
  });
});

test('Should close popover on escape press', async () => {
  setupTest({
    ...createProps(),
    destroyTooltipOnHide: true,
  });

  expect(screen.getByTestId('control-popover')).toBeInTheDocument();
  expect(screen.queryByText('Control Popover Test')).not.toBeInTheDocument();
  userEvent.click(screen.getByTestId('control-popover'));
  expect(await screen.findByText('Control Popover Test')).toBeInTheDocument();

  // Ensure that pressing any other key than escape does nothing
  fireEvent.keyDown(screen.getByTestId('control-popover'), {
    key: 'Enter',
    code: 13,
    charCode: 13,
  });
  expect(await screen.findByText('Control Popover Test')).toBeInTheDocument();

  // Escape should close the popover
  fireEvent.keyDown(screen.getByTestId('control-popover'), {
    key: 'Escape',
    code: 27,
    charCode: 0,
  });

  await waitFor(() => {
    expect(screen.queryByText('Control Popover Test')).not.toBeInTheDocument();
  });
});

test('Controlled mode', async () => {
  const baseProps = {
    ...createProps(),
    destroyTooltipOnHide: true,
    open: false,
  };

  const { rerender } = setupTest(baseProps);

  expect(screen.getByTestId('control-popover')).toBeInTheDocument();
  expect(screen.queryByText('Control Popover Test')).not.toBeInTheDocument();

  rerender(<TestComponent {...baseProps} open />);
  expect(await screen.findByText('Control Popover Test')).toBeInTheDocument();

  rerender(<TestComponent {...baseProps} />);
  await waitFor(() => {
    expect(screen.queryByText('Control Popover Test')).not.toBeInTheDocument();
  });
});
