import React from 'react'
import { render, screen, waitFor } from '@testing-library/react'
import { Provider } from 'react-redux'
import { ParticipantsSpeakerView } from './ParticipantsSpeakerView'
import userEvent from '@testing-library/user-event'
import { initStore } from '../../../testUtils'
import { act } from 'react-dom/test-utils'
import { ParticipantProps } from '../../Participant'
import { activeSpeakersChanged } from '../../../store/session/sessionSlice'

const renderWithParticipants = async (
  numberOfRemoteParticipants: number,
  pageSize?: number,
  activeSpeakers?: string[],
  participantElement?: React.FC<ParticipantProps>
) => {
  const store = initStore(numberOfRemoteParticipants, activeSpeakers)
  const { container } = render(
    <Provider store={store}>
      <ParticipantsSpeakerView pageSize={pageSize} components={{ Participant: participantElement }} />
    </Provider>
  )

  await waitFor(() => {
    expect(screen.getByTestId('participant1Element')).toHaveClass('zocks__active-speaker__participant')
  })

  return { container, store }
}

test('only the local participant is displayed, without the ribbon for other participants if only the local participant is present', async () => {
  await renderWithParticipants(0)

  expect(screen.getByTestId('participant1Element')).toHaveTextContent('Participant name: local')
  expect(screen.queryByRole('button', { name: 'Previous' })).not.toBeInTheDocument()
  expect(screen.queryByRole('button', { name: 'Next' })).not.toBeInTheDocument()
})

test('when the local participant is the only active speaker, still a remote participant is displayed as main speaker', async () => {
  const { container } = await renderWithParticipants(1, 4, ['Participant id: local'])

  const mainSpeaker = container.firstChild
  const others = container.querySelector('.zocks__participants-speaker-view__others')
  expect(mainSpeaker!.firstChild).toHaveTextContent('Participant name: 0')
  expect(others!.firstChild).toHaveTextContent('Participant name: local')
})

test('displays the main speaker, and every participant on the right side when there are 4 participants, sorted by the speakers', async () => {
  const { container } = await renderWithParticipants(3, 4, ['Participant id: 2', 'Participant id: 1'])

  const mainSpeaker = container.firstChild
  const others = container.querySelector('.zocks__participants-speaker-view__others')
  expect(mainSpeaker!.firstChild).toHaveTextContent('Participant name: 2')
  expect(others!.children[0]).toHaveTextContent('Participant name: local')
  expect(others!.children[1]).toHaveTextContent('Participant name: 2')
  expect(others!.children[2]).toHaveTextContent('Participant name: 1')
  expect(others!.children[3]).toHaveTextContent('Participant name: 0')
})

test('clicking the Next button displays the participants on the next page, but the main speaker remains', async () => {
  await renderWithParticipants(5, 4, ['Participant id: 3', 'Participant id: local'])

  const nextButton = screen.getByRole('button', { name: 'Next' })
  expect(nextButton).not.toBeDisabled()

  userEvent.click(nextButton)

  expect(screen.queryByRole('button', { name: 'Next' })).not.toBeInTheDocument()
  const mainSpeaker = '3'
  const firstPageParticipants = ['local', '3', '0', '1']
  const secondPageParticipants = ['2', '4']
  expect(screen.getByTestId('participant1Element')).toHaveTextContent(`Participant name: ${mainSpeaker}`)
  for (const participant of secondPageParticipants) {
    expect(screen.getByText(`Participant name: ${participant}`)).toBeInTheDocument()
  }
  for (const participant of firstPageParticipants.filter((p) => p !== mainSpeaker)) {
    expect(screen.queryByText(`Participant name: ${participant}`)).not.toBeInTheDocument()
  }
})

test('clicking the Previous button displays the participants on the previous page', async () => {
  await renderWithParticipants(5, 4, ['Participant id: 3', 'Participant id: local'])
  userEvent.click(screen.getByRole('button', { name: 'Next' }))

  const previousButton = screen.getByRole('button', { name: 'Previous' })
  expect(previousButton).not.toBeDisabled()
  userEvent.click(previousButton)

  expect(screen.queryByRole('button', { name: 'Previous' })).not.toBeInTheDocument()
  const mainSpeaker = '3'
  const firstPageParticipants = ['local', '3', '0', '1']
  const secondPageParticipants = ['2', '4']
  expect(screen.getByTestId('participant1Element')).toHaveTextContent(`Participant name: ${mainSpeaker}`)
  for (const participant of firstPageParticipants.filter((p) => p !== mainSpeaker)) {
    expect(screen.getByText(`Participant name: ${participant}`)).toBeInTheDocument()
  }
  for (const participant of secondPageParticipants) {
    expect(screen.queryByText(`Participant name: ${participant}`)).not.toBeInTheDocument()
  }
})

test('page size is considered when displaying other participants', async () => {
  await renderWithParticipants(4, 2, ['Participant id: 3'])

  expect(screen.getByTestId('participant1Element')).toHaveTextContent('Participant name: 3')
  expect(screen.getByText('Participant name: local')).toBeInTheDocument()
  expect(screen.queryByText('Participant name: 0')).not.toBeInTheDocument()
  expect(screen.queryByText('Participant name: 1')).not.toBeInTheDocument()
  expect(screen.queryByText('Participant name: 2')).not.toBeInTheDocument()
})

test('if a speaker becomes inactive, they are still considered for the ordering of the participants', async () => {
  const { container, store } = await renderWithParticipants(3, 4, ['Participant id: 2', 'Participant id: 1'])

  act(() => {
    store.dispatch(activeSpeakersChanged(['Participant id: 1']))
  })

  const mainSpeaker = container.firstChild
  const others = container.querySelector('.zocks__participants-speaker-view__others')
  expect(mainSpeaker!.firstChild).toHaveTextContent('Participant name: 1')
  expect(others!.children[0]).toHaveTextContent('Participant name: local')
  expect(others!.children[1]).toHaveTextContent('Participant name: 1')
  expect(others!.children[2]).toHaveTextContent('Participant name: 2')
  expect(others!.children[3]).toHaveTextContent('Participant name: 0')
})

test('the active speaker changes only after a delay', async () => {
  jest.useFakeTimers()
  const { store } = await renderWithParticipants(2, 4, ['Participant id: 0'])
  expect(screen.getByTestId('participant1Element')).toHaveTextContent('Participant name: 0')

  act(() => {
    store.dispatch(activeSpeakersChanged(['Participant id: 1']))
  })

  const participant1Element = screen.getByTestId('participant1Element')
  const participant2Element = screen.getByTestId('participant2Element')

  expect(participant1Element).toHaveTextContent('Participant name: 0')
  expect(participant2Element).toHaveTextContent('Participant name: 1')
  expect(participant1Element).not.toHaveClass('zocks__active-speaker__participant--hidden')
  expect(participant2Element).toHaveClass('zocks__active-speaker__participant--hidden')

  act(() => {
    jest.runOnlyPendingTimers()
  })

  expect(participant2Element).not.toHaveClass('zocks__active-speaker__participant--hidden')
  expect(participant1Element).toHaveClass('zocks__active-speaker__participant--hidden')
  jest.useRealTimers()
})

test('displayed participants are switched when the active speaker changes for multiple times', async () => {
  jest.useFakeTimers()
  const { store } = await renderWithParticipants(2, 4, ['Participant id: 0'])
  expect(screen.getByTestId('participant1Element')).toHaveTextContent('Participant name: 0')
  const participant1Element = screen.getByTestId('participant1Element')
  const participant2Element = screen.getByTestId('participant2Element')

  act(() => {
    store.dispatch(activeSpeakersChanged(['Participant id: 1']))
  })

  act(() => {
    jest.runOnlyPendingTimers()
  })

  expect(participant2Element).not.toHaveClass('zocks__active-speaker__participant--hidden')
  expect(participant1Element).toHaveClass('zocks__active-speaker__participant--hidden')

  act(() => {
    store.dispatch(activeSpeakersChanged(['Participant id: 0']))
  })

  act(() => {
    jest.runOnlyPendingTimers()
  })

  expect(participant1Element).not.toHaveClass('zocks__active-speaker__participant--hidden')
  expect(participant2Element).toHaveClass('zocks__active-speaker__participant--hidden')
  jest.useRealTimers()
})

test('renders a custom participant if a custom participant element is given', async () => {
  await renderWithParticipants(0, 4, [], () => <div>custom participant element</div>)

  expect(screen.getByTestId('participant1Element')).toHaveTextContent('custom participant element')
})
