import {act, render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';

import {
  addErrorMessage,
  addMessage,
  addSuccessMessage,
  clearIndicators,
} from 'sentry/actionCreators/indicator';
import Indicators from 'sentry/components/indicators';
import IndicatorStore from 'sentry/stores/indicatorStore';

// Make sure we use `duration: null` to test add/remove
jest.useFakeTimers();

jest.mock('framer-motion', () => ({
  ...jest.requireActual('framer-motion'),
  AnimatePresence: jest.fn(({children}) => children),
}));

describe('Indicators', function () {
  beforeEach(function () {
    act(() => clearIndicators());
    act(jest.runAllTimers);
  });

  it('renders nothing by default', function () {
    const {container} = render(<Indicators />);
    expect(container).toHaveTextContent('');
  });

  it('has a loading indicator by default', function () {
    const {container} = render(<Indicators />);
    // when "type" is empty, we should treat it as loading state

    act(() => void IndicatorStore.add('Loading'));
    expect(screen.getByTestId('loading-indicator')).toBeInTheDocument();
    expect(container).toHaveTextContent('Loading');
  });

  it('adds and removes a toast by calling IndicatorStore directly', function () {
    const {container} = render(<Indicators />);

    // when "type" is empty, we should treat it as loading state
    let indicator;
    act(() => {
      indicator = IndicatorStore.add('Loading');
    });

    expect(container).toHaveTextContent('Loading');

    // Old indicator gets replaced when a new one is added
    act(() => IndicatorStore.remove(indicator));
    expect(container).toHaveTextContent('');
  });

  // This is a common pattern used throughout the code for API calls
  it('adds and replaces toast by calling IndicatorStore directly', function () {
    const {container} = render(<Indicators />);

    act(() => void IndicatorStore.add('Loading'));
    expect(container).toHaveTextContent('Loading');

    // Old indicator gets replaced when a new one is added
    act(() => void IndicatorStore.add('success', 'success'));
    expect(container).toHaveTextContent('success');
  });

  it('does not have loading indicator when "type" is empty (default)', function () {
    const {container} = render(<Indicators />);

    act(() => addMessage('Loading', '', {duration: null}));
    act(jest.runAllTimers);
    expect(container).toHaveTextContent('Loading');
    expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument();
  });

  it('has a loading indicator when type is "loading"', function () {
    const {container} = render(<Indicators />);

    act(() => addMessage('Loading', 'loading', {duration: null}));
    act(jest.runAllTimers);
    expect(container).toHaveTextContent('Loading');
    expect(screen.getByTestId('loading-indicator')).toBeInTheDocument();
  });

  it('adds and removes toast by calling action creators', function () {
    const {container} = render(<Indicators />);

    // action creators don't return anything
    act(() => addMessage('Loading', '', {duration: null}));
    act(jest.runAllTimers);
    expect(container).toHaveTextContent('Loading');

    // If no indicator is specified, will remove all indicators
    act(() => clearIndicators());
    act(jest.runAllTimers);
    expect(container).toHaveTextContent('');
    expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument();
  });

  it('adds and replaces toast by calling action creators', function () {
    const {container} = render(<Indicators />);

    act(() => addMessage('Loading', '', {duration: null}));
    act(jest.runAllTimers);
    expect(container).toHaveTextContent('Loading');

    // Old indicator gets replaced when a new one is added
    act(() => addMessage('success', 'success', {duration: null}));
    act(jest.runAllTimers);
    expect(container).toHaveTextContent('success');
    expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument();
  });

  it('adds and replaces toasts by calling action creators helpers', async function () {
    const {container} = render(<Indicators />);

    // Old indicator gets replaced when a new one is added
    act(() => addSuccessMessage('success'));

    await waitFor(() => {
      expect(container).toHaveTextContent('success');
    });

    act(() => clearIndicators());
    act(() => addErrorMessage('error'));
    await waitFor(() => {
      expect(container).toHaveTextContent('error');
    });
  });

  it('appends toasts', function () {
    const {container} = render(<Indicators />);

    act(() => addMessage('Loading', '', {append: true, duration: null}));
    act(jest.runAllTimers);
    expect(screen.getByTestId('toast')).toHaveTextContent('Loading');

    act(() => addMessage('Success', 'success', {append: true, duration: null}));
    act(jest.runAllTimers);
    // Toasts get appended to the end
    expect(screen.getByTestId('toast')).toHaveTextContent('Loading');
    expect(screen.getByTestId('toast-success')).toHaveTextContent('Success');

    act(() => addMessage('Error', 'error', {append: true, duration: null}));
    act(jest.runAllTimers);
    // Toasts get appended to the end
    expect(screen.getByTestId('toast')).toHaveTextContent('Loading');
    expect(screen.getByTestId('toast-success')).toHaveTextContent('Success');
    expect(screen.getByTestId('toast-error')).toHaveTextContent('Error');

    // clears all toasts
    act(() => clearIndicators());
    act(jest.runAllTimers);
    expect(container).toHaveTextContent('');
    expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument();
  });

  it('dismisses on click', function () {
    const {container} = render(<Indicators />);

    act(() => addMessage('Loading', '', {append: true, duration: null}));
    act(jest.runAllTimers);
    expect(screen.getByTestId('toast')).toHaveTextContent('Loading');

    userEvent.click(screen.getByTestId('toast'));
    act(jest.runAllTimers);
    expect(container).toHaveTextContent('');
    expect(screen.queryByTestId('toast')).not.toBeInTheDocument();
  });

  it('hides after 10s', function () {
    const {container} = render(<Indicators />);

    act(() => addMessage('Duration', '', {append: true, duration: 10000}));
    act(() => jest.advanceTimersByTime(9000));
    expect(screen.getByTestId('toast')).toHaveTextContent('Duration');

    // Still visible
    act(() => jest.advanceTimersByTime(999));
    expect(screen.getByTestId('toast')).toHaveTextContent('Duration');

    act(() => jest.advanceTimersByTime(2));
    expect(container).toHaveTextContent('');
    expect(screen.queryByTestId('toast')).not.toBeInTheDocument();
  });
});
