import React from 'react'
import { Provider } from 'react-redux'
import { configureStore } from '@reduxjs/toolkit'
import userEvent from '@testing-library/user-event'
import { act, render, screen, waitFor } from '@testing-library/react'
import { SessionState, StreamKind, StreamSource } from '@zocks-communications/client-sdk-js'
import {
  createParticipants,
  setupMediaDevicesMock,
  setupMockLocalMediaStream,
  setupMockVirtualBackground,
} from '../../testUtils'
import { useZocksActions } from '../../store'
import { participantLocalTrackPublished, setSessionState, setRoomId } from '../../store/session/sessionSlice'
import { OneOnOneMeeting } from './OneOnOneMeeting'
import { zocksStoreConfig } from '../../app/store'

const TestComponent = () => {
  const actions = useZocksActions({
    apiBaseUrl: 'https://test-zocks-api-base-url',
    accountId: 'testAccountId',
    appInfo: 'OneOnOne Meeting Test App'
  })
  return <OneOnOneMeeting zocksActions={actions} joinOptions={{ roomId: 'testRoomId' }} />
}

const renderOneOnOneParticipantView = async () => {
  const store = configureStore(zocksStoreConfig)

  const { container } = render(
    <Provider store={store}>
      <TestComponent />
    </Provider>
  )
  expect(await screen.findByRole('button', { name: 'Audio devices' })).toBeInTheDocument()
  return { container, store }
}

const joinRoom = async (numberOfRemoteParticipants: number) => {
  const { container, store } = await renderOneOnOneParticipantView()

  const joinButton = screen.getByRole('button', { name: 'Join' })
  await userEvent.click(joinButton)

  createParticipants(store, numberOfRemoteParticipants)

  await act(async () => {
    await Promise.resolve()
    store.dispatch(setSessionState(SessionState.CONNECTED))
    store.dispatch(setRoomId('mockSessionId'))
  })

  return { container, store }
}

beforeEach(() => {
  setupMediaDevicesMock()
})

test('displays join button when not joined', async () => {
  await renderOneOnOneParticipantView()
  expect(await screen.findByRole('button', { name: 'Audio devices' })).toBeInTheDocument()
  expect(await screen.findByRole('button', { name: 'Join' })).toBeInTheDocument()
})

test('starts and stops the local video stream when clicking the enable and disable video buttons', async () => {
  setupMockLocalMediaStream()

  await renderOneOnOneParticipantView()

  const enableVideoButton = screen.getByRole('button', { name: 'Enable Video' })
  userEvent.click(enableVideoButton)
  await waitFor(() => {
    expect(screen.getByTestId('videoElement')).toBeInTheDocument()
  })

  const videoDevicesButton = screen.getByRole('button', { name: 'Video devices' })
  userEvent.click(videoDevicesButton)
  expect(await screen.findByText('Test Camera 1')).toBeInTheDocument()

  const disableVideoButton = screen.getByRole('button', { name: 'Disable Video' })
  expect(disableVideoButton).toBeInTheDocument()
  userEvent.click(disableVideoButton)
  await waitFor(() => {
    expect(screen.queryByTestId('videoElement')).not.toBeInTheDocument()
  })
  expect(screen.getByRole('button', { name: 'Enable Video' })).toBeInTheDocument()
})

test('change camera to other video device from video devices list', async () => {
  const { mockLocalMediaStream } = setupMockLocalMediaStream()

  await renderOneOnOneParticipantView()

  const enableVideoButton = screen.getByRole('button', { name: 'Enable Video' })
  userEvent.click(enableVideoButton)
  await waitFor(() => {
    expect(screen.getByTestId('videoElement')).toBeInTheDocument()
  })

  const videoDevicesButton = screen.getByRole('button', { name: 'Video devices' })
  userEvent.click(videoDevicesButton)
  const testCamera2Button = await screen.findByText('Test Camera 2')
  expect(testCamera2Button).toBeInTheDocument()
  userEvent.click(testCamera2Button)

  await waitFor(() => expect(mockLocalMediaStream.attach).toHaveBeenCalledTimes(1))
})

test('turn on and off the blur background from video devices list', async () => {
  const { mockStartVirtualBackground } = setupMockVirtualBackground()
  setupMockLocalMediaStream()

  await renderOneOnOneParticipantView()

  const enableVideoButton = screen.getByRole('button', { name: 'Enable Video' })
  userEvent.click(enableVideoButton)
  await waitFor(() => {
    expect(screen.getByTestId('videoElement')).toBeInTheDocument()
  })

  const videoDevicesButton = screen.getByRole('button', { name: 'Video devices' })
  userEvent.click(videoDevicesButton)
  const blurButton = screen.getByText('Blur My Background')
  userEvent.click(blurButton)
  await waitFor(() => expect(mockStartVirtualBackground).toHaveBeenCalledTimes(1))

  userEvent.click(videoDevicesButton)
  const unblurButton = screen.getByText('Unblur My Background')
  userEvent.click(unblurButton)
  await waitFor(() => expect(mockStartVirtualBackground).toHaveBeenCalledTimes(1))
})

test('turn on and off the audio stream with the audio button, check audio devices list', async () => {
  const { mockCreateMediaStream, mockLocalMediaStream: mockLocalAudioStream } = setupMockLocalMediaStream()

  await renderOneOnOneParticipantView()

  const unmuteButton = screen.getByRole('button', { name: 'Unmute' })
  userEvent.click(unmuteButton)
  expect(await screen.findByRole('button', { name: 'Mute' })).toBeInTheDocument()
  await waitFor(() => {
    expect(mockCreateMediaStream).toHaveBeenCalledTimes(1)
  })

  await waitFor(() => {
    expect(mockCreateMediaStream).toHaveBeenCalledWith({
      audio: { deviceId: 'testMicrophone1MediaDeviceId' },
      video: false,
    })
  })

  const audioDevicesButton = screen.getByRole('button', { name: 'Audio devices' })
  userEvent.click(audioDevicesButton)
  expect(await screen.findByText('Test Microphone 1')).toBeInTheDocument()
  userEvent.click(await screen.findByText('Test Microphone 2'))
  await waitFor(() => {
    expect(mockCreateMediaStream).toHaveBeenCalledTimes(2)
  })
  const muteButton = screen.getByRole('button', { name: 'Mute' })
  expect(muteButton).toBeInTheDocument()
  userEvent.click(muteButton)
  expect(mockLocalAudioStream.stopStream).toHaveBeenCalledTimes(2)
  expect(screen.getByRole('button', { name: 'Unmute' })).toBeInTheDocument()
})

test('displays only local participant when remote is not connected', async () => {
  const { container } = await joinRoom(0)
  setupMockLocalMediaStream()

  expect(screen.getByText('Waiting for the other participant to join.')).toBeInTheDocument()
  expect(await container.querySelector('.zocks__active-speaker__container')).not.toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Audio devices' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Video devices' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Share Screen' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Leave' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Full Screen' })).toBeInTheDocument()
})

test('displays remote participant when connected', async () => {
  const { container } = await joinRoom(1)
  setupMockLocalMediaStream()

  const mainSpeaker = container.firstChild
  expect(mainSpeaker!.firstChild).toHaveTextContent('Participant name: 0')
  expect(await container.querySelector('.zocks__oneonone-participant-view__floating-participant')).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Audio devices' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Video devices' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Share Screen' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Leave' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Full Screen' })).toBeInTheDocument()
})

test('displays screen share when start screen sharing', async () => {
  const { container, store } = await joinRoom(1)
  setupMockLocalMediaStream()

  await store.dispatch(
    participantLocalTrackPublished({
      trackId: 'testLocalScreenShareTrackId',
      trackSource: StreamSource.SCREEN_SHARE,
      trackKind: StreamKind.VIDEO,
      trackIsMuted: false,
    })
  )
  expect(container.querySelector('.zocks__oneonone-main-participant__screen-share')).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Audio devices' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Video devices' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Stop Sharing' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Leave' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Full Screen' })).toBeInTheDocument()
})

test('displays full screen when full screen button clicked', async () => {
  const { container } = await joinRoom(1)
  setupMockLocalMediaStream()
  const fullScreenButton = await screen.findByRole('button', { name: 'Full Screen' })
  await userEvent.click(fullScreenButton)

  expect(container.querySelector('.zocks__oneonone-meeting--full-screen')).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Audio devices' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Video devices' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Share Screen' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Leave' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Exit Full Screen' })).toBeInTheDocument()
})
