import { act, render, screen } from '@testing-library/react';
import React, { Component } from 'react';
import { StoreState } from 'app/types';
import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';
import AppRootPage from './AppRootPage';
import { getPluginSettings } from './PluginSettingsCache';
import { importAppPlugin } from './plugin_loader';
import { getMockPlugin } from './__mocks__/pluginMocks';
import { AppPlugin, PluginType, AppRootProps, NavModelItem } from '@grafana/data';
import { updateLocation } from 'app/core/actions';
import { createRootReducer } from 'app/core/reducers/root';
import { createStore } from 'redux';

jest.mock('./PluginSettingsCache', () => ({
  getPluginSettings: jest.fn(),
}));
jest.mock('./plugin_loader', () => ({
  importAppPlugin: jest.fn(),
}));

const importAppPluginMock = importAppPlugin as jest.Mock<
  ReturnType<typeof importAppPlugin>,
  Parameters<typeof importAppPlugin>
>;

const getPluginSettingsMock = getPluginSettings as jest.Mock<
  ReturnType<typeof getPluginSettings>,
  Parameters<typeof getPluginSettings>
>;

const initialState: Partial<StoreState> = {
  location: {
    routeParams: {
      pluginId: 'my-awesome-plugin',
      slug: 'my-awesome-plugin',
    },
    query: {},
    path: '/a/my-awesome-plugin',
    url: '',
    replace: false,
    lastUpdated: 1,
  },
};

function renderWithStore(soreState: Partial<StoreState> = initialState) {
  const store = configureStore<StoreState>()(soreState as StoreState);
  render(
    <Provider store={store}>
      <AppRootPage />
    </Provider>
  );
  return store;
}

describe('AppRootPage', () => {
  beforeEach(() => {
    jest.resetAllMocks();
  });

  it('should not mount plugin twice if nav is changed', async () => {
    // reproduces https://github.com/grafana/grafana/pull/28105

    getPluginSettingsMock.mockResolvedValue(
      getMockPlugin({
        type: PluginType.app,
        enabled: true,
      })
    );

    let timesMounted = 0;

    // a very basic component that does what most plugins do:
    // immediately update nav on mounting
    class RootComponent extends Component<AppRootProps> {
      componentDidMount() {
        timesMounted++;
        const node: NavModelItem = {
          text: 'My Great plugin',
          children: [
            {
              text: 'A page',
              url: '/apage',
              id: 'a',
            },
            {
              text: 'Another page',
              url: '/anotherpage',
              id: 'b',
            },
          ],
        };
        this.props.onNavChanged({
          main: node,
          node,
        });
      }
      render() {
        return <p>my great plugin</p>;
      }
    }

    const plugin = new AppPlugin();
    plugin.root = RootComponent;

    importAppPluginMock.mockResolvedValue(plugin);

    renderWithStore();

    // check that plugin and nav links were rendered, and plugin is mounted only once
    await screen.findByText('my great plugin');
    await screen.findAllByRole('link', { name: /A page/ });
    await screen.findAllByRole('link', { name: /Another page/ });
    expect(timesMounted).toEqual(1);
  });

  it('should not render component if not at plugin path', async () => {
    getPluginSettingsMock.mockResolvedValue(
      getMockPlugin({
        type: PluginType.app,
        enabled: true,
      })
    );

    let timesRendered = 0;
    class RootComponent extends Component<AppRootProps> {
      render() {
        timesRendered += 1;
        return <p>my great component</p>;
      }
    }

    const plugin = new AppPlugin();
    plugin.root = RootComponent;

    importAppPluginMock.mockResolvedValue(plugin);

    const store = createStore(createRootReducer());
    store.dispatch(updateLocation({ path: '/a/foo' }));
    render(
      <Provider store={store}>
        <AppRootPage />
      </Provider>
    );
    await screen.findByText('my great component');

    // renders the first time
    expect(timesRendered).toEqual(1);

    await act(async () => {
      await store.dispatch(
        updateLocation({
          path: '/foo',
        })
      );
    });

    expect(timesRendered).toEqual(1);

    await act(async () => {
      await store.dispatch(
        updateLocation({
          path: '/a/foo',
        })
      );
    });

    expect(timesRendered).toEqual(2);
  });
});
