import { Provider } from 'react-redux'
import { configureStore, EnhancedStore } from '@reduxjs/toolkit'
import { act, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import {
  addRemoteParticipant,
  participantLocalTrackPublished,
  participantTrackPublished,
} from '../../store/session/sessionSlice'
import { StreamKind, StreamSource, VirtualBackgroundType } from '@zocks-communications/client-sdk-js'
import { zocksStoreConfig } from '../../app/store'
import { setupMediaDevicesMock } from '../../testUtils'
import { ZocksMediaActions, ZocksTranscriptionActions } from '../../store'
import { RoomControls } from './RoomControls'
import { setCurrentVirtualBackground } from '../../store/virtualBackground/virtualBackgroundSlice'
import { setDisplayTranscriptions } from '../../store/transcript/transcriptSlice'

const mockSetVirtualBackground = jest.fn()
const mockClearVirtualBackground = jest.fn()

const mockMediaActions: ZocksMediaActions = {
  publishVideo: jest.fn(),
  unPublishVideo: jest.fn(),
  publishAudio: jest.fn(),
  unPublishAudio: jest.fn(),
  publishScreenShare: jest.fn(),
  unPublishScreenShare: jest.fn(),
  switchActiveDevice: jest.fn(),
  setVirtualBackground: async (v) => mockSetVirtualBackground(v),
  clearVirtualBackground: async () => mockClearVirtualBackground(),
}

const mockTranscriptionActions: ZocksTranscriptionActions = {
  enableTranscription: jest.fn(),
  disableTranscription: jest.fn(),
  startTranscription: jest.fn(),
  stopTranscription: jest.fn(),
}

const mockLeaveAction = jest.fn()

beforeEach(() => setupMediaDevicesMock())

afterEach(() => jest.restoreAllMocks())

const renderWithState = async (store: EnhancedStore) => {
  render(
    <Provider store={store}>
      <RoomControls
        mediaActions={mockMediaActions}
        transcriptionActions={mockTranscriptionActions}
        leaveAction={mockLeaveAction}
      />
    </Provider>
  )
  expect(await screen.findByRole('button', { name: 'Audio devices' })).toBeInTheDocument()
}

test('renders a muted audio control button, a muted video control button, a screen share button, device selectors, and a leave button by default', async () => {
  const store = configureStore(zocksStoreConfig)
  await renderWithState(store)

  expect(screen.getByRole('button', { name: 'Unmute' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Audio devices' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Enable Video' })).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: 'Audio devices' })).toBeDisabled()
  expect(screen.getByRole('button', { name: 'Video devices' })).toBeDisabled()
})

test('clicking on the muted audio button calls the publish audio media action', async () => {
  const store = configureStore(zocksStoreConfig)
  await renderWithState(store)

  const audioButton = screen.getByRole('button', { name: 'Unmute' })
  userEvent.click(audioButton)

  expect(mockMediaActions.publishAudio).toHaveBeenCalledTimes(1)
})

test('when the local participant has unmuted audio, renders an unmuted audio button, which calls the unpublish audio media action', async () => {
  const store = configureStore(zocksStoreConfig)
  store.dispatch(
    participantLocalTrackPublished({
      trackId: 'testTrackId',
      trackKind: StreamKind.AUDIO,
      trackSource: StreamSource.MICROPHONE,
      trackIsMuted: false,
    })
  )
  await renderWithState(store)

  const audioButton = screen.getByRole('button', { name: 'Mute' })
  expect(audioButton).toBeInTheDocument()

  userEvent.click(audioButton)
  expect(mockMediaActions.unPublishAudio).toHaveBeenCalledTimes(1)
})

test('clicking on the muted video button calls the publish video media action', async () => {
  const store = configureStore(zocksStoreConfig)
  await renderWithState(store)

  const videoButton = screen.getByRole('button', { name: 'Enable Video' })
  userEvent.click(videoButton)

  expect(mockMediaActions.publishVideo).toHaveBeenCalledTimes(1)
})

test('when the local participant has unmuted video, renders an unmuted audio button, which calls the unpublish video media action', async () => {
  const store = configureStore(zocksStoreConfig)
  store.dispatch(
    participantLocalTrackPublished({
      trackId: 'testTrackId',
      trackKind: StreamKind.VIDEO,
      trackSource: StreamSource.CAMERA,
      trackIsMuted: false,
    })
  )
  await renderWithState(store)

  const videoButton = screen.getByRole('button', { name: 'Disable Video' })
  expect(videoButton).toBeInTheDocument()

  userEvent.click(videoButton)
  expect(mockMediaActions.unPublishVideo).toHaveBeenCalledTimes(1)
})

test('clicking on the screen share button calls the publish screen share media action', async () => {
  const store = configureStore(zocksStoreConfig)
  await renderWithState(store)

  const screenShareButton = screen.getByRole('button', { name: 'Share Screen' })
  userEvent.click(screenShareButton)

  expect(mockMediaActions.publishScreenShare).toHaveBeenCalledTimes(1)
})

test('when the local participant is sharing their screen, renders a Stop Sharing button, which calls the unpublish screen share media action', async () => {
  const store = configureStore(zocksStoreConfig)
  store.dispatch(
    participantLocalTrackPublished({
      trackId: 'testTrackId',
      trackKind: StreamKind.VIDEO,
      trackSource: StreamSource.SCREEN_SHARE,
      trackIsMuted: false,
    })
  )
  await renderWithState(store)

  const screenShareButton = screen.getByRole('button', { name: 'Stop Sharing' })
  expect(screenShareButton).toBeInTheDocument()
  expect(screenShareButton).not.toBeDisabled()

  userEvent.click(screenShareButton)
  expect(mockMediaActions.unPublishScreenShare).toHaveBeenCalledTimes(1)
})

test('when a remote participant is sharing their screen, the Share Screen button is disabled', async () => {
  const store = configureStore(zocksStoreConfig)
  store.dispatch(
    addRemoteParticipant({ participantId: 'testRemoteParticipantId', participantName: 'testRemoteParticipantName' })
  )
  store.dispatch(
    participantTrackPublished({
      participantId: 'testRemoteParticipantId',
      trackId: 'testTrackId',
      trackKind: StreamKind.VIDEO,
      trackSource: StreamSource.SCREEN_SHARE,
      trackIsMuted: false,
      trackIsSubscribed: true,
    })
  )
  await renderWithState(store)

  expect(screen.getByRole('button', { name: 'Share Screen' })).toBeDisabled()
})

test('clicking on the leave button calls the leave action', async () => {
  const store = configureStore(zocksStoreConfig)
  await renderWithState(store)

  const leaveButton = screen.getByRole('button', { name: 'Leave' })
  userEvent.click(leaveButton)

  expect(mockLeaveAction).toHaveBeenCalledTimes(1)
})

test('clicking on an audio device calls the switchActiveDevice action', async () => {
  const store = configureStore(zocksStoreConfig)
  act(() => {
    store.dispatch(
      participantLocalTrackPublished({
        trackId: 'testTrackId',
        trackKind: StreamKind.AUDIO,
        trackSource: StreamSource.MICROPHONE,
        trackIsMuted: false,
      })
    )
  })
  await renderWithState(store)

  userEvent.click(screen.getByRole('button', { name: 'Audio devices' }))
  userEvent.click(screen.getByText('Test Microphone 2'))

  await waitFor(() => expect(mockMediaActions.switchActiveDevice).toHaveBeenCalledTimes(1))
  expect(mockMediaActions.switchActiveDevice).toHaveBeenCalledWith('audioinput', 'testMicrophone2MediaDeviceId')
})

test('clicking on a video device calls the switchActiveDevice action', async () => {
  const store = configureStore(zocksStoreConfig)
  act(() => {
    store.dispatch(
      participantLocalTrackPublished({
        trackId: 'testTrackId',
        trackKind: StreamKind.VIDEO,
        trackSource: StreamSource.CAMERA,
        trackIsMuted: false,
      })
    )
  })
  await renderWithState(store)

  userEvent.click(screen.getByRole('button', { name: 'Video devices' }))
  userEvent.click(screen.getByText('Test Camera 2'))

  await waitFor(() => expect(mockMediaActions.switchActiveDevice).toHaveBeenCalledTimes(1))
  expect(mockMediaActions.switchActiveDevice).toHaveBeenCalledWith('videoinput', 'testCamera2MediaDeviceId')
})

test('selecting a new virtual background calls the setVirtualBackground action', async () => {
  const store = configureStore(zocksStoreConfig)
  store.dispatch(
    participantLocalTrackPublished({
      trackId: 'testTrackId',
      trackKind: StreamKind.VIDEO,
      trackSource: StreamSource.CAMERA,
      trackIsMuted: false,
    })
  )
  await renderWithState(store)

  userEvent.click(screen.getByRole('button', { name: 'Video devices' }))
  userEvent.click(screen.getByText('Blur My Background'))

  await waitFor(() => expect(mockSetVirtualBackground).toHaveBeenCalledTimes(1))
  expect(mockSetVirtualBackground).toHaveBeenCalledWith(VirtualBackgroundType.BLUR)
})

test('removing the virtual background calls the clearVirtualBackground action', async () => {
  const store = configureStore(zocksStoreConfig)
  store.dispatch(
    participantLocalTrackPublished({
      trackId: 'testTrackId',
      trackKind: StreamKind.VIDEO,
      trackSource: StreamSource.CAMERA,
      trackIsMuted: false,
    })
  )
  store.dispatch(setCurrentVirtualBackground(VirtualBackgroundType.BLUR))
  await renderWithState(store)

  userEvent.click(screen.getByRole('button', { name: 'Video devices' }))
  userEvent.click(screen.getByText('Unblur My Background'))

  await waitFor(() => expect(mockClearVirtualBackground).toHaveBeenCalledTimes(1))
})

test('enabling the transcription while the audio is on calls the enableTranscription and startTranscription actions', async () => {
  const store = configureStore(zocksStoreConfig)
  store.dispatch(
    participantLocalTrackPublished({
      trackId: 'testTrackId',
      trackKind: StreamKind.AUDIO,
      trackSource: StreamSource.MICROPHONE,
      trackIsMuted: false,
    })
  )
  await renderWithState(store)

  userEvent.click(screen.getByRole('button', { name: 'Audio devices' }))
  userEvent.click(screen.getByText('Show Transcriptions'))

  userEvent.click(screen.getByRole('button', { name: 'Audio devices' }))
  expect(screen.getByText('Hide Transcriptions')).toBeInTheDocument()
})

test('disabling the transcription calls the stopTranscription and disableTranscription actions', async () => {
  const store = configureStore(zocksStoreConfig)
  store.dispatch(
    participantLocalTrackPublished({
      trackId: 'testTrackId',
      trackKind: StreamKind.AUDIO,
      trackSource: StreamSource.MICROPHONE,
      trackIsMuted: false,
    })
  )
  store.dispatch(setDisplayTranscriptions(true))
  await renderWithState(store)

  userEvent.click(screen.getByRole('button', { name: 'Audio devices' }))
  userEvent.click(screen.getByText('Hide Transcriptions'))

  userEvent.click(screen.getByRole('button', { name: 'Audio devices' }))
  expect(screen.getByText('Show Transcriptions')).toBeInTheDocument()
})
