import { render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { Provider } from 'react-redux';

import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';

import { backendSrv } from '../../core/services/backend_srv';
import { configureStore } from '../../store/configureStore';

import { SignupInvitedPage, Props } from './SignupInvited';

jest.mock('app/core/core', () => ({
  contextSrv: {
    user: { orgName: 'Invited to Org Name' },
  },
}));

jest.mock('@grafana/runtime', () => ({
  ...(jest.requireActual('@grafana/runtime') as unknown as object),
  getBackendSrv: () => backendSrv,
}));

const defaultGet = {
  email: 'some.user@localhost',
  name: 'Some User',
  invitedBy: 'Invited By User',
  username: 'someuser',
};

async function setupTestContext({ get = defaultGet }: { get?: typeof defaultGet | null } = {}) {
  jest.clearAllMocks();
  const store = configureStore();

  const getSpy = jest.spyOn(backendSrv, 'get');
  getSpy.mockResolvedValue(get);

  const postSpy = jest.spyOn(backendSrv, 'post');
  postSpy.mockResolvedValue([]);

  const props: Props = {
    ...getRouteComponentProps({
      match: {
        params: { code: 'some code' },
      } as any,
    }),
  };

  render(
    <Provider store={store}>
      <SignupInvitedPage {...props} />
    </Provider>
  );

  await waitFor(() => expect(getSpy).toHaveBeenCalled());
  expect(getSpy).toHaveBeenCalledTimes(1);

  return { getSpy, postSpy };
}

describe('SignupInvitedPage', () => {
  describe('when initialized but invite data has not been retrieved yet', () => {
    it('then it should not render', async () => {
      await setupTestContext({ get: null });

      expect(screen.queryByText(/email/i)).not.toBeInTheDocument();
    });
  });

  describe('when initialized and invite data has been retrieved', () => {
    it('then the greeting should be correct', async () => {
      await setupTestContext();

      expect(
        screen.getByRole('heading', {
          name: /hello some user\./i,
        })
      ).toBeInTheDocument();
    });

    it('then the invited by should be correct', async () => {
      await setupTestContext();

      const view = screen.getByText(
        /has invited you to join grafana and the organization please complete the following and choose a password to accept your invitation and continue:/i
      );

      expect(within(view).getByText(/invited by user/i)).toBeInTheDocument();
    });

    it('then the organization invited to should be correct', async () => {
      await setupTestContext();

      const view = screen.getByText(
        /has invited you to join grafana and the organization please complete the following and choose a password to accept your invitation and continue:/i
      );

      expect(within(view).getByText(/invited to org name/i)).toBeInTheDocument();
    });

    it('then the form should include form data', async () => {
      await setupTestContext();

      expect(screen.getByPlaceholderText(/email@example\.com/i)).toHaveValue('some.user@localhost');
      expect(screen.getByPlaceholderText(/name \(optional\)/i)).toHaveValue('Some User');
      expect(screen.getByPlaceholderText(/username/i)).toHaveValue('some.user@localhost');
      expect(screen.getByPlaceholderText(/password/i)).toHaveValue('');
    });
  });

  describe('when user submits the form and the required fields are not filled in', () => {
    it('then required fields should show error messages and nothing should be posted', async () => {
      const { postSpy } = await setupTestContext({ get: { email: '', invitedBy: '', name: '', username: '' } });

      await userEvent.click(screen.getByRole('button', { name: /sign up/i }));

      await waitFor(() => expect(screen.getByText(/email is required/i)).toBeInTheDocument());
      expect(screen.getByText(/username is required/i)).toBeInTheDocument();
      expect(screen.getByText(/password is required/i)).toBeInTheDocument();
      expect(postSpy).toHaveBeenCalledTimes(0);
    });
  });

  describe('when user submits the form and the required fields are filled in', () => {
    it('then correct form data should be posted', async () => {
      const { postSpy } = await setupTestContext();

      await userEvent.type(screen.getByPlaceholderText(/password/i), 'pass@word1');
      await userEvent.click(screen.getByRole('button', { name: /sign up/i }));

      await waitFor(() => expect(postSpy).toHaveBeenCalledTimes(1));
      expect(postSpy).toHaveBeenCalledWith('/api/user/invite/complete', {
        email: 'some.user@localhost',
        name: 'Some User',
        username: 'some.user@localhost',
        password: 'pass@word1',
        inviteCode: 'some code',
      });
    });
  });
});
