import React from 'react'
import { act, render, screen } from '@testing-library/react'
import { Participant } from './Participant'
import {
  activeSpeakersChanged,
  setLocalParticipantId,
  setRoomId,
  setSessionState,
} from '../../store/session/sessionSlice'
import { ConnectionQuality, SessionState } from '@zocks-communications/client-sdk-js'
import { setupZocksSdkMocks } from '../../testUtils'
import { configureStore, EnhancedStore } from '@reduxjs/toolkit'
import { Provider } from 'react-redux'
import { zocksStoreConfig } from '../../app/store'
import { ParticipantExtensionProps } from './model'
import { ParticipantModel } from '../../store'

const renderWithState = (
  store: EnhancedStore,
  participant: ParticipantModel,
  withAudio: boolean = true,
  extensionElement?: React.FC<ParticipantExtensionProps>
) => {
  return render(
    <Provider store={store}>
      <Participant participant={participant} withAudio={withAudio} components={{ Extension: extensionElement }} />
    </Provider>
  )
}

const testLocalParticipant: ParticipantModel = {
  id: 'testLocalParticipantId',
  name: 'testLocalParticipantName',
  tracks: {
    audioTracks: [
      {
        id: 'testLocalAudioTrackId',
        isMuted: false,
        isSubscribed: true,
      },
    ],
    videoTracks: [
      {
        id: 'testLocalVideoTrackId',
        isMuted: false,
        isSubscribed: true,
      },
    ],
    screenShareTrack: null,
  },
  connectionQuality: ConnectionQuality.EXCELLENT,
}

const testRemoteParticipant: ParticipantModel = {
  id: 'testRemoteParticipantId',
  name: 'testRemoteParticipantName',
  tracks: {
    audioTracks: [
      {
        id: 'testRemoteAudioTrackId',
        isMuted: false,
        isSubscribed: true,
      },
    ],
    videoTracks: [
      {
        id: 'testRemoteVideoTrackId',
        isMuted: false,
        isSubscribed: true,
      },
    ],
    screenShareTrack: null,
  },
  connectionQuality: ConnectionQuality.GOOD,
}

test('renders the participant id and connection quality, and no media if they are not available', () => {
  const store = configureStore(zocksStoreConfig)
  store.dispatch(setSessionState(SessionState.CONNECTED))
  store.dispatch(setRoomId('testRoomId'))
  renderWithState(store, testLocalParticipant)

  expect(screen.getByText('testLocalParticipantName')).toBeInTheDocument()
  expect(screen.queryByTestId('videoElement')).not.toBeInTheDocument()
  expect(screen.queryByTestId('audioElement')).not.toBeInTheDocument()
})

test('renders audio and video tags for a remote participant if both are available, and attaches the corresponding tracks to them', () => {
  const { mockRemoteAudioTrack, mockRemoteVideoTrack } = setupZocksSdkMocks()
  const store = configureStore(zocksStoreConfig)
  store.dispatch(setSessionState(SessionState.CONNECTED))
  store.dispatch(setRoomId('testRoomId'))
  renderWithState(store, testRemoteParticipant)

  expect(screen.getByTestId('audioElement')).toBeInTheDocument()
  expect(screen.getByTestId('videoElement')).toBeInTheDocument()
  expect(mockRemoteAudioTrack.attach).toHaveBeenCalledTimes(1)
  expect(mockRemoteVideoTrack.attach).toHaveBeenCalledTimes(1)
  expect(mockRemoteAudioTrack.attach.mock.calls[0][0]).toBeInstanceOf(HTMLAudioElement)
  expect(mockRemoteVideoTrack.attach.mock.calls[0][0]).toBeInstanceOf(HTMLVideoElement)
})

test('renders only video tag for the local participant even if there is also audio, and attaches the corresponding track to it', () => {
  const { mockLocalAudioTrack, mockLocalVideoTrack } = setupZocksSdkMocks()
  const store = configureStore(zocksStoreConfig)
  store.dispatch(setSessionState(SessionState.CONNECTED))
  store.dispatch(setRoomId('testRoomId'))
  store.dispatch(setLocalParticipantId('testLocalParticipantId'))
  renderWithState(store, testLocalParticipant)

  expect(screen.getByTestId('videoElement')).toBeInTheDocument()
  expect(screen.queryByTestId('audioElement')).not.toBeInTheDocument()
  expect(mockLocalAudioTrack.attach).not.toHaveBeenCalled()
  expect(mockLocalVideoTrack.attach).toHaveBeenCalledTimes(1)
  expect(mockLocalVideoTrack.attach.mock.calls[0][0]).toBeInstanceOf(HTMLVideoElement)
})

test('does not render video or audio tags for muted tracks', () => {
  const { mockRemoteAudioTrack, mockRemoteVideoTrack } = setupZocksSdkMocks()
  const store = configureStore(zocksStoreConfig)
  store.dispatch(setSessionState(SessionState.CONNECTED))
  store.dispatch(setRoomId('testRoomId'))
  renderWithState(store, {
    ...testRemoteParticipant,
    tracks: {
      audioTracks: [
        {
          id: 'testRemoteAudioTrackId',
          isMuted: true,
          isSubscribed: true,
        },
      ],
      videoTracks: [
        {
          id: 'testRemoteVideoTrackId',
          isMuted: true,
          isSubscribed: true,
        },
      ],
      screenShareTrack: null,
    },
  })

  expect(screen.queryByTestId('audioElement')).not.toBeInTheDocument()
  expect(screen.queryByTestId('videoElement')).not.toBeInTheDocument()
  expect(mockRemoteAudioTrack.attach).not.toHaveBeenCalled()
  expect(mockRemoteVideoTrack.attach).not.toHaveBeenCalled()
})

test('does not render video or audio tags for unsubscribed tracks', () => {
  const { mockRemoteAudioTrack, mockRemoteVideoTrack } = setupZocksSdkMocks()
  const store = configureStore(zocksStoreConfig)
  store.dispatch(setSessionState(SessionState.CONNECTED))
  store.dispatch(setRoomId('testRoomId'))
  renderWithState(store, {
    ...testRemoteParticipant,
    tracks: {
      audioTracks: [
        {
          id: 'testRemoteAudioTrackId',
          isMuted: false,
          isSubscribed: false,
        },
      ],
      videoTracks: [
        {
          id: 'testRemoteVideoTrackId',
          isMuted: false,
          isSubscribed: false,
        },
      ],
      screenShareTrack: null,
    },
  })

  expect(screen.queryByTestId('audioElement')).not.toBeInTheDocument()
  expect(screen.queryByTestId('videoElement')).not.toBeInTheDocument()
  expect(mockRemoteAudioTrack.attach).not.toHaveBeenCalled()
  expect(mockRemoteVideoTrack.attach).not.toHaveBeenCalled()
})

test('has custom style when the participant is speaking', () => {
  const store = configureStore(zocksStoreConfig)
  store.dispatch(setSessionState(SessionState.CONNECTED))
  store.dispatch(setRoomId('testRoomId'))
  store.dispatch(setLocalParticipantId('testLocalParticipantId'))
  store.dispatch(activeSpeakersChanged(['testLocalParticipantId']))
  const { container } = renderWithState(store, testLocalParticipant)

  expect(container.querySelector('.zocks__participant__video-container--speaking')).toBeInTheDocument()
})

test('when the participant was speaking previously, and they are not an active speaker anymore, the speaking indicator custom style is removed with a delay', () => {
  jest.useFakeTimers()
  const store = configureStore(zocksStoreConfig)
  store.dispatch(setSessionState(SessionState.CONNECTED))
  store.dispatch(setRoomId('testRoomId'))
  store.dispatch(setLocalParticipantId('testLocalParticipantId'))
  store.dispatch(activeSpeakersChanged(['testLocalParticipantId']))
  const { container } = renderWithState(store, testLocalParticipant)

  act(() => {
    store.dispatch(activeSpeakersChanged([]))
  })

  expect(container.querySelector('.zocks__participant__video-container--speaking')).toBeInTheDocument()

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

  expect(container.querySelector('.zocks__participant__video-container--speaking')).not.toBeInTheDocument()
  jest.useRealTimers()
})

test('when the participant was speaking previously, but they become inactive, then active in a short period of time, the speaking indicator style stays', () => {
  jest.useFakeTimers()
  const store = configureStore(zocksStoreConfig)
  store.dispatch(setSessionState(SessionState.CONNECTED))
  store.dispatch(setRoomId('testRoomId'))
  store.dispatch(setLocalParticipantId('testLocalParticipantId'))
  store.dispatch(activeSpeakersChanged(['testLocalParticipantId']))
  const { container } = renderWithState(store, testLocalParticipant)

  act(() => {
    store.dispatch(activeSpeakersChanged([]))
  })

  expect(container.querySelector('.zocks__participant__video-container--speaking')).toBeInTheDocument()

  act(() => {
    jest.advanceTimersByTime(1000)
  })

  expect(container.querySelector('.zocks__participant__video-container--speaking')).toBeInTheDocument()
  jest.useRealTimers()
})

test('does not render an audio tag if withAudio is false', () => {
  setupZocksSdkMocks()
  const store = configureStore(zocksStoreConfig)
  store.dispatch(setSessionState(SessionState.CONNECTED))
  store.dispatch(setRoomId('testRoomId'))
  renderWithState(store, testRemoteParticipant, false)

  expect(screen.queryByTestId('audioElement')).not.toBeInTheDocument()
  expect(screen.getByTestId('videoElement')).toBeInTheDocument()
})

test('renders the extension element if given', () => {
  const store = configureStore(zocksStoreConfig)
  store.dispatch(setSessionState(SessionState.CONNECTED))
  store.dispatch(setRoomId('testRoomId'))
  renderWithState(store, testLocalParticipant, false, () => <div>extension</div>)

  expect(screen.getByText('extension')).toBeInTheDocument()
})
