import { configureStore, EnhancedStore } from '@reduxjs/toolkit'
import { act, render, screen } from '@testing-library/react'
import { Provider } from 'react-redux'
import React from 'react'
import { TranscriptionDisplay } from './TranscriptionDisplay'
import { zocksStoreConfig } from '../../app/store'
import { setDisplayTranscriptions, transcriptionReceived } from '../../store/transcript/transcriptSlice'
import { TranscriptionResultItem } from '@zocks-communications/client-sdk-js'

beforeEach(() => jest.useFakeTimers().setSystemTime(new Date('2023-02-03T00:00:00.000Z')))

afterEach(() => jest.useRealTimers())

const createTranscriptionResultItems = (transcript: string): TranscriptionResultItem[] => {
  return transcript.split(' ').map((t) => ({ content: t, isStable: true }))
}

const baseMockTranscriptionResult = {
  resultId: 'testResultId',
  createdAt: '2023-02-03T00:00:00.000Z',
  isFinal: false,
  startTime: 0,
  endTime: 2,
}

const renderWithState = (store: EnhancedStore, wordsInLine?: number) => {
  store.dispatch(setDisplayTranscriptions(true))
  return render(
    <Provider store={store}>
      <TranscriptionDisplay participantId='testLocalParticipantId' wordsInLine={wordsInLine} />
    </Provider>
  )
}

test('does not render anything if there is neither partial nor full transcription for the participant', () => {
  const store = configureStore(zocksStoreConfig)
  const { container } = renderWithState(store)

  expect(container).toBeEmptyDOMElement()
})

test('does not render anything if displaying transcriptions is turned off', async () => {
  const store = configureStore(zocksStoreConfig)
  store.dispatch(
    transcriptionReceived({
      participantId: 'testLocalParticipantId',
      transcriptionResult: {
        ...baseMockTranscriptionResult,
        transcript: 'test transcript',
        items: [
          {
            content: 'test',
            isStable: true,
          },
          {
            content: 'transcript',
            isStable: false,
          },
        ],
      },
    })
  )
  const { container } = renderWithState(store)

  await act(() => {
    store.dispatch(setDisplayTranscriptions(false))
  })

  expect(container).toBeEmptyDOMElement()
})

test('displays the partial transcript if only the partial transcript is available', () => {
  const store = configureStore(zocksStoreConfig)
  store.dispatch(
    transcriptionReceived({
      participantId: 'testLocalParticipantId',
      transcriptionResult: {
        ...baseMockTranscriptionResult,
        transcript: 'test transcript',
        items: [
          {
            content: 'test',
            isStable: true,
          },
          {
            content: 'transcript',
            isStable: false,
          },
        ],
      },
    })
  )
  renderWithState(store)

  expect(screen.getByText('test')).toBeInTheDocument()
  expect(screen.getByText('test')).not.toHaveClass('transcription-display__partial-result')
  expect(screen.getByText('transcript')).toBeInTheDocument()
  expect(screen.getByText('transcript')).toHaveClass('transcription-display__partial-result')
})

test('displays the last final transcript result if only the final transcript is available', () => {
  const store = configureStore(zocksStoreConfig)
  store.dispatch(
    transcriptionReceived({
      participantId: 'testLocalParticipantId',
      transcriptionResult: {
        ...baseMockTranscriptionResult,
        isFinal: true,
        transcript: 'test',
        items: createTranscriptionResultItems('test'),
      },
    })
  )
  renderWithState(store)

  expect(screen.getByText('test')).toBeInTheDocument()
  expect(screen.getByText('test')).not.toHaveClass('transcription-display__partial-result')
})

test('displays both the partial and last final transcript if both are available', () => {
  const store = configureStore(zocksStoreConfig)
  store.dispatch(
    transcriptionReceived({
      participantId: 'testLocalParticipantId',
      transcriptionResult: {
        ...baseMockTranscriptionResult,
        isFinal: true,
        transcript: 'final',
        items: createTranscriptionResultItems('final'),
      },
    })
  )
  store.dispatch(
    transcriptionReceived({
      participantId: 'testLocalParticipantId',
      transcriptionResult: {
        ...baseMockTranscriptionResult,
        transcript: 'test transcript',
        items: [
          {
            content: 'test',
            isStable: true,
          },
          {
            content: 'transcript',
            isStable: false,
          },
        ],
      },
    })
  )
  renderWithState(store)

  expect(screen.getByText('final')).toBeInTheDocument()
  expect(screen.getByText('final')).not.toHaveClass('transcription-display__partial-result')
  expect(screen.getByText('test')).toBeInTheDocument()
  expect(screen.getByText('test')).not.toHaveClass('transcription-display__partial-result')
  expect(screen.getByText('transcript')).toBeInTheDocument()
  expect(screen.getByText('transcript')).toHaveClass('transcription-display__partial-result')
})

test('punctuations are merged to the previous content when displaying the transcription', () => {
  const store = configureStore(zocksStoreConfig)
  store.dispatch(
    transcriptionReceived({
      participantId: 'testLocalParticipantId',
      transcriptionResult: {
        ...baseMockTranscriptionResult,
        isFinal: true,
        transcript: 'test.',
        items: [
          { content: 'test', isStable: true },
          { content: '.', isStable: true },
        ],
      },
    })
  )
  renderWithState(store)

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

test('displays two lines if there are enough words to display', () => {
  const store = configureStore(zocksStoreConfig)
  store.dispatch(
    transcriptionReceived({
      participantId: 'testLocalParticipantId',
      transcriptionResult: {
        ...baseMockTranscriptionResult,
        isFinal: true,
        transcript: '1 2 3 4 5 6 7 8 9 10 11',
        items: createTranscriptionResultItems('1 2 3 4 5 6 7 8 9 10 11'),
      },
    })
  )
  const { container } = renderWithState(store)

  for (const c of '1 2 3 4 5 6 7 8 9 10 11'.split(' ')) {
    expect(screen.getByText(c)).toBeInTheDocument()
  }
  expect(container.getElementsByClassName('transcription-display__line')).toHaveLength(2)
})

test('displays only a specified number of words per line', () => {
  const store = configureStore(zocksStoreConfig)
  store.dispatch(
    transcriptionReceived({
      participantId: 'testLocalParticipantId',
      transcriptionResult: {
        ...baseMockTranscriptionResult,
        isFinal: true,
        transcript: '1 2 3 4',
        items: createTranscriptionResultItems('1 2 3 4'),
      },
    })
  )
  const { container } = renderWithState(store, 2)

  for (const c of '1 2 3 4'.split(' ')) {
    expect(screen.getByText(c)).toBeInTheDocument()
  }
  expect(container.getElementsByClassName('transcription-display__line')).toHaveLength(2)
})

test('when there are more words than the available space, the first line is filled but the second line fill as words arrive', () => {
  const store = configureStore(zocksStoreConfig)
  store.dispatch(
    transcriptionReceived({
      participantId: 'testLocalParticipantId',
      transcriptionResult: {
        ...baseMockTranscriptionResult,
        transcript: '1 2 3 4 5',
        items: createTranscriptionResultItems('1 2 3 4 5'),
      },
    })
  )
  const { container } = renderWithState(store, 2)

  expect(screen.queryByText('1')).not.toBeInTheDocument()
  expect(screen.queryByText('2')).not.toBeInTheDocument()
  expect(screen.getByText('3')).toBeInTheDocument()
  expect(screen.getByText('4')).toBeInTheDocument()
  expect(screen.getByText('5')).toBeInTheDocument()
  expect(container.getElementsByClassName('transcription-display__line')).toHaveLength(2)

  act(() => {
    store.dispatch(
      transcriptionReceived({
        participantId: 'testLocalParticipantId',
        transcriptionResult: {
          ...baseMockTranscriptionResult,
          isFinal: true,
          transcript: '1 2 3 4 5 6',
          items: createTranscriptionResultItems('1 2 3 4 5 6'),
        },
      })
    )
  })

  expect(screen.queryByText('1')).not.toBeInTheDocument()
  expect(screen.queryByText('2')).not.toBeInTheDocument()
  expect(screen.getByText('3')).toBeInTheDocument()
  expect(screen.getByText('4')).toBeInTheDocument()
  expect(screen.getByText('5')).toBeInTheDocument()
  expect(screen.getByText('6')).toBeInTheDocument()
  expect(container.getElementsByClassName('transcription-display__line')).toHaveLength(2)
})

test('when a new final transcript is received, displays it along the current partial transcript', () => {
  const store = configureStore(zocksStoreConfig)
  store.dispatch(
    transcriptionReceived({
      participantId: 'testLocalParticipantId',
      transcriptionResult: {
        ...baseMockTranscriptionResult,
        transcript: 'partial1',
        items: [{ content: 'partial1', isStable: false }],
      },
    })
  )
  renderWithState(store)

  expect(screen.getByText('partial1')).toBeInTheDocument()

  act(() => {
    store.dispatch(
      transcriptionReceived({
        participantId: 'testLocalParticipantId',
        transcriptionResult: {
          ...baseMockTranscriptionResult,
          isFinal: true,
          transcript: 'final1',
          items: createTranscriptionResultItems('final1'),
        },
      })
    )
    store.dispatch(
      transcriptionReceived({
        participantId: 'testLocalParticipantId',
        transcriptionResult: {
          ...baseMockTranscriptionResult,
          transcript: 'partial2',
          items: [{ content: 'partial2', isStable: false }],
        },
      })
    )
  })

  expect(screen.getByText('final1')).toBeInTheDocument()
  expect(screen.getByText('partial2')).toBeInTheDocument()
  expect(screen.getByText('partial2')).toHaveClass('transcription-display__partial-result')
})

test('when there is no partial transcript anymore, hides the transcript after a delay', () => {
  jest.useFakeTimers()
  const store = configureStore(zocksStoreConfig)
  store.dispatch(
    transcriptionReceived({
      participantId: 'testLocalParticipantId',
      transcriptionResult: {
        ...baseMockTranscriptionResult,
        isFinal: true,
        transcript: 'final1',
        items: createTranscriptionResultItems('final1'),
      },
    })
  )
  store.dispatch(
    transcriptionReceived({
      participantId: 'testLocalParticipantId',
      transcriptionResult: {
        ...baseMockTranscriptionResult,
        isFinal: false,
        transcript: 'partial',
        items: [{ content: 'partial', isStable: false }],
      },
    })
  )
  const { container } = renderWithState(store)

  expect(screen.getByText('final1')).toBeInTheDocument()
  expect(screen.getByText('partial')).toBeInTheDocument()

  act(() => {
    store.dispatch(
      transcriptionReceived({
        participantId: 'testLocalParticipantId',
        transcriptionResult: {
          ...baseMockTranscriptionResult,
          isFinal: true,
          transcript: 'final2',
          items: createTranscriptionResultItems('final2'),
        },
      })
    )
  })

  expect(screen.queryByText('partial')).not.toBeInTheDocument()
  expect(screen.getByText('final1')).toBeInTheDocument()
  expect(screen.getByText('final2')).toBeInTheDocument()

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

  expect(container).toBeEmptyDOMElement()

  jest.useRealTimers()
})

test('hides transcriptions after the delay has passed even if no partial result arrives', async () => {
  const store = configureStore(zocksStoreConfig)
  const { container } = renderWithState(store)
  const shouldHideAfterDelay = 3000

  act(() => {
    store.dispatch(
      transcriptionReceived({
        participantId: 'testLocalParticipantId',
        transcriptionResult: {
          ...baseMockTranscriptionResult,
          createdAt: '2023-02-03T00:00:00.000Z',
          isFinal: true,
          transcript: 'final',
          items: createTranscriptionResultItems('final'),
        },
      })
    )
  })
  expect(screen.getByText('final')).toBeInTheDocument()

  act(() => {
    jest.advanceTimersByTime(shouldHideAfterDelay)
  })
  expect(container).toBeEmptyDOMElement()
})

test('hides transcriptions after the delay has passed even if a single partial result arrived', async () => {
  const store = configureStore(zocksStoreConfig)
  const { container } = renderWithState(store)
  const shouldHideAfterDelay = 3000

  act(() => {
    store.dispatch(
      transcriptionReceived({
        participantId: 'testLocalParticipantId',
        transcriptionResult: {
          ...baseMockTranscriptionResult,
          createdAt: '2023-02-03T00:00:00.000Z',
          isFinal: false,
          transcript: 'partial',
          items: createTranscriptionResultItems('partial'),
        },
      })
    )
  })
  expect(screen.getByText('partial')).toBeInTheDocument()

  act(() => {
    jest.advanceTimersByTime(shouldHideAfterDelay)
  })
  expect(container).toBeEmptyDOMElement()
})

test('displays only final transcriptions created after the hide delay', () => {
  const store = configureStore(zocksStoreConfig)
  store.dispatch(
    transcriptionReceived({
      participantId: 'testLocalParticipantId',
      transcriptionResult: {
        ...baseMockTranscriptionResult,
        createdAt: '2023-02-02T23:59:57.000Z',
        isFinal: true,
        transcript: 'final1',
        items: createTranscriptionResultItems('final1'),
      },
    })
  )
  store.dispatch(
    transcriptionReceived({
      participantId: 'testLocalParticipantId',
      transcriptionResult: {
        ...baseMockTranscriptionResult,
        createdAt: '2023-02-02T23:59:58.000Z',
        isFinal: true,
        transcript: 'final2',
        items: createTranscriptionResultItems('final2'),
      },
    })
  )
  renderWithState(store)

  expect(screen.getByText('final2')).toBeInTheDocument()
  expect(screen.queryByText('final1')).not.toBeInTheDocument()
})
