import React from 'react'
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { CustomizableChatInput } from './CustomizableChatInput'
import { mock } from 'jest-mock-extended'
import { ChatMessageInputProps } from '../models'
import { ParticipantModel, PUBLIC_CHAT_RECIPIENT } from '../../../store'

const testRemoteParticipants: ParticipantModel[] = [
  mock<ParticipantModel>({ id: 'testRemoteParticipantId1', name: 'Test Remote Participant 1' }),
  mock<ParticipantModel>({ id: 'testRemoteParticipantId2', name: 'Test Remote Participant 2' }),
]

test('renders an input field, a recipient selector, an emoji selector, an upload button and a send button', () => {
  render(<CustomizableChatInput messageRecipient={PUBLIC_CHAT_RECIPIENT} uploadAttachment={jest.fn()} />)

  expect(screen.getByRole('textbox')).toBeInTheDocument()
  expect(screen.getByRole('combobox')).toBeInTheDocument()
  expect(screen.getByLabelText('Toggle emoji selector')).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Upload a file' })).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Send' })).toBeInTheDocument()
})

test('calls onRecipientSelected callback when a recipient is selected via the combobox', () => {
  const mockOnRecipientSelected = jest.fn()
  render(
    <CustomizableChatInput
      messageRecipient={PUBLIC_CHAT_RECIPIENT}
      remoteParticipants={testRemoteParticipants}
      onRecipientSelected={mockOnRecipientSelected}
    />
  )

  userEvent.selectOptions(
    screen.getByRole('combobox'),
    screen.getByRole('option', { name: 'Test Remote Participant 1' })
  )

  expect(mockOnRecipientSelected).toHaveBeenCalledTimes(1)
  expect(mockOnRecipientSelected).toHaveBeenCalledWith('testRemoteParticipantId1')
})

test('sends the entered public message without attachment', () => {
  const mockSendPublicMessage = jest.fn()
  render(<CustomizableChatInput messageRecipient={PUBLIC_CHAT_RECIPIENT} sendPublicMessage={mockSendPublicMessage} />)

  const chatMessageInput = screen.getByRole('textbox')
  userEvent.type(chatMessageInput, 'test public message{enter}')

  expect(mockSendPublicMessage).toHaveBeenCalledTimes(1)
  expect(mockSendPublicMessage).toHaveBeenCalledWith('test public message', undefined)
  expect(screen.getByRole('textbox')).toHaveTextContent('')
})

test('sends the entered public message with attachment', async () => {
  const mockSendPublicMessage = jest.fn()
  render(
    <CustomizableChatInput
      messageRecipient={PUBLIC_CHAT_RECIPIENT}
      uploadAttachment={jest.fn().mockResolvedValue('test-file-id')}
      sendPublicMessage={mockSendPublicMessage}
    />
  )

  const mockFile = mock<File>({ name: 'test-file-name.jpg', size: 5, type: 'image/jpeg' })
  const fileInput = screen.getByTestId('hidden-file-input')
  userEvent.upload(fileInput, mockFile)
  await screen.findByText('test-file-name.jpg')
  const chatMessageInput = screen.getByRole('textbox')
  userEvent.type(chatMessageInput, 'test public message{enter}')

  expect(mockSendPublicMessage).toHaveBeenCalledTimes(1)
  expect(mockSendPublicMessage).toHaveBeenCalledWith('test public message', {
    fileName: 'test-file-name.jpg',
    fileSize: 5,
    contentType: 'image/jpeg',
    fileId: 'test-file-id',
  })
  expect(screen.getByRole('textbox')).toHaveTextContent('')
})

test('sends the entered private message without attachment', () => {
  const mockSendPrivateMessage = jest.fn()
  render(
    <CustomizableChatInput
      messageRecipient={'testRemoteParticipantId1'}
      remoteParticipants={testRemoteParticipants}
      sendPrivateMessage={mockSendPrivateMessage}
    />
  )

  userEvent.selectOptions(
    screen.getByRole('combobox'),
    screen.getByRole('option', { name: 'Test Remote Participant 1' })
  )
  const chatMessageInput = screen.getByRole('textbox')
  userEvent.type(chatMessageInput, 'test private message')
  userEvent.click(screen.getByRole('button', { name: 'Send' }))

  expect(mockSendPrivateMessage).toHaveBeenCalledTimes(1)
  expect(mockSendPrivateMessage).toHaveBeenCalledWith('test private message', testRemoteParticipants[0], undefined)
  expect(screen.getByRole('textbox')).toHaveTextContent('')
})

test('sends the entered private message with attachment', async () => {
  const mockSendPrivateMessage = jest.fn()
  render(
    <CustomizableChatInput
      messageRecipient={'testRemoteParticipantId1'}
      uploadAttachment={jest.fn().mockResolvedValue('test-file-id')}
      remoteParticipants={testRemoteParticipants}
      sendPrivateMessage={mockSendPrivateMessage}
    />
  )

  userEvent.selectOptions(
    screen.getByRole('combobox'),
    screen.getByRole('option', { name: 'Test Remote Participant 1' })
  )
  const mockFile = mock<File>({ name: 'test-file-name.jpg', size: 5, type: 'image/jpeg' })
  const fileInput = screen.getByTestId('hidden-file-input')
  userEvent.upload(fileInput, mockFile)
  await screen.findByText('test-file-name.jpg')
  const chatMessageInput = screen.getByRole('textbox')
  userEvent.type(chatMessageInput, 'test private message')
  userEvent.click(screen.getByRole('button', { name: 'Send' }))

  expect(mockSendPrivateMessage).toHaveBeenCalledTimes(1)
  expect(mockSendPrivateMessage).toHaveBeenCalledWith('test private message', testRemoteParticipants[0], {
    fileName: 'test-file-name.jpg',
    fileSize: 5,
    contentType: 'image/jpeg',
    fileId: 'test-file-id',
  })
  expect(screen.getByRole('textbox')).toHaveTextContent('')
})

test('does not send a message with only whitespaces', () => {
  const mockSendPublicMessage = jest.fn()
  render(<CustomizableChatInput messageRecipient={PUBLIC_CHAT_RECIPIENT} sendPublicMessage={mockSendPublicMessage} />)

  const chatMessageInput = screen.getByRole('textbox')
  userEvent.type(chatMessageInput, ' \r\n\t{enter}')

  expect(mockSendPublicMessage).toHaveBeenCalledTimes(0)
})

test('after selecting an emoji, the message input will have focus', () => {
  render(<CustomizableChatInput messageRecipient={PUBLIC_CHAT_RECIPIENT} />)

  userEvent.click(screen.getByLabelText('Toggle emoji selector'))
  userEvent.click(screen.getByLabelText('Insert 😂 emoji'))

  const chatMessageInput = screen.getByRole('textbox')
  expect(chatMessageInput).toHaveTextContent('😂')
  expect(chatMessageInput).toHaveFocus()
})

test('selecting an emoji inserts the emoji at the end of the current message with a space', () => {
  render(<CustomizableChatInput messageRecipient={PUBLIC_CHAT_RECIPIENT} />)

  userEvent.type(screen.getByRole('textbox'), 'chat message')
  userEvent.click(screen.getByLabelText('Toggle emoji selector'))
  userEvent.click(screen.getByLabelText('Insert 😂 emoji'))

  const chatMessageInput = screen.getByRole('textbox')
  expect(chatMessageInput).toHaveTextContent('chat message 😂')
})

test('if there is a recipient selected but they disconnect, all participants will be selected as recipient', () => {
  const mockOnRecipientSelected = jest.fn()
  const { rerender } = render(
    <CustomizableChatInput messageRecipient={'testRemoteParticipantId1'} remoteParticipants={testRemoteParticipants} />
  )
  userEvent.selectOptions(
    screen.getByRole('combobox'),
    screen.getByRole('option', { name: 'Test Remote Participant 1' })
  )

  rerender(
    <CustomizableChatInput
      messageRecipient={'testRemoteParticipantId1'}
      remoteParticipants={[testRemoteParticipants[1]]}
      onRecipientSelected={mockOnRecipientSelected}
    />
  )

  expect(screen.getByRole('combobox')).toHaveValue(PUBLIC_CHAT_RECIPIENT)
  expect(mockOnRecipientSelected).toHaveBeenCalledTimes(1)
  expect(mockOnRecipientSelected).toHaveBeenCalledWith(PUBLIC_CHAT_RECIPIENT)
})

test('displays an uploaded attachment and focuses on the input on successful attachment upload', async () => {
  const mockUploadAttachment = jest.fn().mockResolvedValue('test-file-id')
  render(<CustomizableChatInput messageRecipient={PUBLIC_CHAT_RECIPIENT} uploadAttachment={mockUploadAttachment} />)

  const mockFile = mock<File>({ name: 'test-file-name.jpg' })
  const fileInput = screen.getByTestId('hidden-file-input')
  userEvent.upload(fileInput, mockFile)

  expect(mockUploadAttachment).toHaveBeenCalledTimes(1)
  expect(mockUploadAttachment).toHaveBeenCalledWith(mockFile, undefined)
  await screen.findByText('test-file-name.jpg')
  expect(screen.getByRole('textbox')).toHaveFocus()
})

test('displays an error on failed attachment upload', async () => {
  const mockUploadAttachment = jest.fn().mockRejectedValue('')
  render(<CustomizableChatInput messageRecipient={PUBLIC_CHAT_RECIPIENT} uploadAttachment={mockUploadAttachment} />)

  const mockFile = mock<File>()
  const fileInput = screen.getByTestId('hidden-file-input')
  userEvent.upload(fileInput, mockFile)

  expect(mockUploadAttachment).toHaveBeenCalledTimes(1)
  expect(mockUploadAttachment).toHaveBeenCalledWith(mockFile, undefined)
  await screen.findByText('Error while uploading attachment.')
})

test('renders a custom message input element if a custom input is given', () => {
  const DummyChatMessageInput = () =>
    React.forwardRef<HTMLElement, ChatMessageInputProps>(() => <div>Dummy component</div>)
  render(
    <CustomizableChatInput
      messageRecipient={PUBLIC_CHAT_RECIPIENT}
      components={{ ChatMessageInput: DummyChatMessageInput() }}
    />
  )

  expect(screen.getByText('Dummy component')).toBeInTheDocument()
  expect(screen.queryByRole('textbox')).not.toBeInTheDocument()
})

test('renders a custom send button element if a send button is given', () => {
  const DummySendButton = () => <div>Dummy component</div>
  render(
    <CustomizableChatInput messageRecipient={PUBLIC_CHAT_RECIPIENT} components={{ SendButton: DummySendButton }} />
  )

  expect(screen.getByText('Dummy component')).toBeInTheDocument()
  expect(screen.queryByRole('button', { name: 'Send' })).not.toBeInTheDocument()
})

test('renders a custom recipient selector element if a custom recipient selector is given', () => {
  const DummyRecipientSelector = () => <div>Dummy component</div>
  render(
    <CustomizableChatInput
      messageRecipient={PUBLIC_CHAT_RECIPIENT}
      components={{ RecipientSelector: DummyRecipientSelector }}
    />
  )

  expect(screen.getByText('Dummy component')).toBeInTheDocument()
  expect(screen.queryByRole('combobox')).not.toBeInTheDocument()
})

test('renders a custom emoji selector element if a custom emoji selector is given', () => {
  const DummyEmojiSelector = () => <div>Dummy component</div>
  render(
    <CustomizableChatInput
      messageRecipient={PUBLIC_CHAT_RECIPIENT}
      components={{ EmojiSelector: DummyEmojiSelector }}
    />
  )

  expect(screen.getByText('Dummy component')).toBeInTheDocument()
  expect(screen.queryByLabelText('Toggle emoji selector')).not.toBeInTheDocument()
})

test('renders a custom attachment uploader element if a custom attachment uploader is given', () => {
  const DummyAttachmentUploader = () => <div>Dummy component</div>
  render(
    <CustomizableChatInput
      messageRecipient={PUBLIC_CHAT_RECIPIENT}
      uploadAttachment={jest.fn()}
      components={{ AttachmentUploader: DummyAttachmentUploader }}
    />
  )

  expect(screen.getByText('Dummy component')).toBeInTheDocument()
  expect(screen.queryByRole('button', { name: 'Upload a file' })).not.toBeInTheDocument()
})

test('renders a custom attachment display element if a custom attachment display is given', async () => {
  const DummyAttachmentDisplay = () => <div>Dummy component</div>
  const mockUploadAttachment = jest.fn().mockResolvedValue('test-file-id')
  render(
    <CustomizableChatInput
      messageRecipient={PUBLIC_CHAT_RECIPIENT}
      uploadAttachment={mockUploadAttachment}
      components={{ AttachmentDisplay: DummyAttachmentDisplay }}
    />
  )
  const mockFile = mock<File>({ name: 'test-file-name.jpg' })
  const fileInput = screen.getByTestId('hidden-file-input')
  userEvent.upload(fileInput, mockFile)

  await waitFor(() => expect(screen.getByRole('button', { name: 'Upload a file' })).not.toBeDisabled())
  expect(screen.getByText('Dummy component')).toBeInTheDocument()
})
