import { setupMediaDevicesMock, setupZocksSdkMocks } from '../../testUtils'
import { configureStore, EnhancedStore } from '@reduxjs/toolkit'
import { act, render, screen } from '@testing-library/react'
import { Provider } from 'react-redux'
import { ManualSubscription } from './ManualSubscription'
import { SessionEvent, SessionState } from '@zocks-communications/client-sdk-js'
import { zocksStoreConfig } from '../../app/store'
import userEvent from '@testing-library/user-event'
import {
  addRemoteParticipant,
  setLocalParticipantId,
  setLocalParticipantName,
  setRoomId,
  setSessionState,
} from '../../store/session/sessionSlice'

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

const renderWithState = (store: EnhancedStore) => {
  render(
    <Provider store={store}>
      <ManualSubscription />
    </Provider>
  )
}

const joinRoom = async () => {
  const sdkMocks = setupZocksSdkMocks()
  const store = configureStore(zocksStoreConfig)
  renderWithState(store)

  const roomIdInput = screen.getByLabelText('Enter a Meeting ID')
  userEvent.clear(roomIdInput)
  userEvent.type(roomIdInput, 'test room id')

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

  const { mockLocalParticipant, mockRemoteParticipant, mockSession } = sdkMocks
  await act(async () => {
    await Promise.resolve()
    store.dispatch(setLocalParticipantId(mockLocalParticipant.id))
    store.dispatch(setLocalParticipantName(mockLocalParticipant.name))
    store.dispatch(setSessionState(SessionState.CONNECTED))
    store.dispatch(setRoomId(mockSession.room.id!))
    store.dispatch(
      addRemoteParticipant({ participantId: mockRemoteParticipant.id, participantName: mockRemoteParticipant.name })
    )
  })

  return { store, sdkMocks }
}

test('renders the join page if the local participant is not connected', async () => {
  const store = configureStore(zocksStoreConfig)
  renderWithState(store)

  expect(await screen.findByLabelText('Enter a Meeting ID')).toBeInTheDocument()
  expect(await screen.findByRole('button', { name: 'Join the Meeting' })).toBeInTheDocument()
})

test('renders the participants and media controls if the local participant is connected', async () => {
  await joinRoom()

  expect(screen.getByRole('button', { name: 'Unmute' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Enable Video' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Share Screen' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Leave' })).toBeInTheDocument()
  expect(screen.getByText('testLocalParticipantName')).toBeInTheDocument()
})

test('when a remote participant does not have any tracks, no subscribe buttons are displayed', async () => {
  await joinRoom()

  expect(screen.getByText('testRemoteParticipantName')).toBeInTheDocument()
  expect(screen.queryByRole('button', { name: 'Subscribe', exact: false })).not.toBeInTheDocument()
  expect(screen.queryByRole('button', { name: 'Unsubscribe', exact: false })).not.toBeInTheDocument()
})

test('when a remote participant publishes an audio track, the subscribe audio button is displayed', async () => {
  const {
    sdkMocks: { mockRemoteParticipant, mockRemoteAudioTrack, mockEventBus, mockSession },
  } = await joinRoom()

  mockRemoteAudioTrack.isSubscribed = false
  act(() => {
    mockEventBus.emit(
      SessionEvent.ParticipantTrackPublished,
      mockRemoteAudioTrack,
      mockRemoteParticipant,
      mockSession.room
    )
  })

  expect(screen.getByRole('button', { name: 'Subscribe audio' })).toBeInTheDocument()
})

test('when a remote participant publishes a video track, the subscribe video button is displayed', async () => {
  const {
    sdkMocks: { mockRemoteParticipant, mockRemoteVideoTrack, mockEventBus },
  } = await joinRoom()

  mockRemoteVideoTrack.isSubscribed = false
  act(() => {
    mockEventBus.emit(SessionEvent.ParticipantTrackPublished, mockRemoteVideoTrack, mockRemoteParticipant)
  })

  expect(screen.getByRole('button', { name: 'Subscribe video' })).toBeInTheDocument()
})

test('when a remote participant publishes a screen share track, the subscribe screen share button is displayed', async () => {
  const {
    sdkMocks: { mockRemoteParticipant, mockRemoteScreenShareTrack, mockEventBus },
  } = await joinRoom()

  mockRemoteScreenShareTrack.isSubscribed = false
  act(() => {
    mockEventBus.emit(SessionEvent.ParticipantTrackPublished, mockRemoteScreenShareTrack, mockRemoteParticipant)
  })

  expect(screen.getByRole('button', { name: 'Subscribe screen share' })).toBeInTheDocument()
})

test('when clicking the subscribe audio button, the audio track is subscribed', async () => {
  const {
    sdkMocks: { mockRemoteParticipant, mockRemoteAudioTrack, mockEventBus },
  } = await joinRoom()

  mockRemoteAudioTrack.isSubscribed = false
  act(() => {
    mockEventBus.emit(SessionEvent.ParticipantTrackPublished, mockRemoteAudioTrack, mockRemoteParticipant)
  })
  userEvent.click(screen.getByRole('button', { name: 'Subscribe audio' }))

  expect(mockRemoteAudioTrack.subscribe).toHaveBeenCalledTimes(1)
})

test('when clicking the subscribe video button, the video track is subscribed', async () => {
  const {
    sdkMocks: { mockRemoteParticipant, mockRemoteVideoTrack, mockEventBus },
  } = await joinRoom()

  mockRemoteVideoTrack.isSubscribed = false
  act(() => {
    mockEventBus.emit(SessionEvent.ParticipantTrackPublished, mockRemoteVideoTrack, mockRemoteParticipant)
  })
  userEvent.click(screen.getByRole('button', { name: 'Subscribe video' }))

  expect(mockRemoteVideoTrack.subscribe).toHaveBeenCalledTimes(1)
})

test('when clicking the subscribe screen share button, the screen share track is subscribed', async () => {
  const {
    sdkMocks: { mockRemoteParticipant, mockRemoteScreenShareTrack, mockEventBus },
  } = await joinRoom()

  mockRemoteScreenShareTrack.isSubscribed = false
  act(() => {
    mockEventBus.emit(SessionEvent.ParticipantTrackPublished, mockRemoteScreenShareTrack, mockRemoteParticipant)
  })
  userEvent.click(screen.getByRole('button', { name: 'Subscribe screen share' }))

  expect(mockRemoteScreenShareTrack.subscribe).toHaveBeenCalledTimes(1)
})

test('when clicking the unsubscribe audio button, the audio track is unsubscribed', async () => {
  const {
    sdkMocks: { mockRemoteParticipant, mockRemoteAudioTrack, mockEventBus },
  } = await joinRoom()

  act(() => {
    mockEventBus.emit(SessionEvent.ParticipantTrackPublished, mockRemoteAudioTrack, mockRemoteParticipant)
  })
  userEvent.click(screen.getByRole('button', { name: 'Unsubscribe audio' }))

  expect(mockRemoteAudioTrack.unsubscribe).toHaveBeenCalledTimes(1)
})

test('when clicking the unsubscribe video button, the video track is unsubscribed', async () => {
  const {
    sdkMocks: { mockRemoteParticipant, mockRemoteVideoTrack, mockEventBus },
  } = await joinRoom()

  act(() => {
    mockEventBus.emit(SessionEvent.ParticipantTrackPublished, mockRemoteVideoTrack, mockRemoteParticipant)
  })
  userEvent.click(screen.getByRole('button', { name: 'Unsubscribe video' }))

  expect(mockRemoteVideoTrack.unsubscribe).toHaveBeenCalledTimes(1)
})

test('when clicking the unsubscribe screen share button, the video track is unsubscribed', async () => {
  const {
    sdkMocks: { mockRemoteParticipant, mockRemoteScreenShareTrack, mockEventBus },
  } = await joinRoom()

  act(() => {
    mockEventBus.emit(SessionEvent.ParticipantTrackPublished, mockRemoteScreenShareTrack, mockRemoteParticipant)
  })
  userEvent.click(screen.getByRole('button', { name: 'Unsubscribe screen share' }))

  expect(mockRemoteScreenShareTrack.unsubscribe).toHaveBeenCalledTimes(1)
})
