import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { mock } from 'jest-mock-extended'
import { ChatAttachmentModel, ParticipantModel } from '../../../store'
import { AttachmentDisplay, AttachmentUploader } from './Attachment'

test('renders a button and a hidden file input element', () => {
  render(<AttachmentUploader uploadAttachment={jest.fn()} />)

  expect(screen.getByRole('button', { name: 'Upload a file' })).toBeInTheDocument()
  expect(screen.getByTestId('hidden-file-input')).toBeInTheDocument()
  expect(screen.queryByRole('input')).not.toBeInTheDocument()
  userEvent.click(screen.getByRole('button', { name: 'Upload a file' }))
})

test('on successful file upload, onAttachmentUploadStarted and onAttachmentUploadFinished are called', async () => {
  const mockUploadAttachment = jest.fn().mockResolvedValue('test-file-id')
  const mockOnAttachmentUploadStarted = jest.fn()
  const mockOnAttachmentUploadFinished = jest.fn()
  render(
    <AttachmentUploader
      uploadAttachment={mockUploadAttachment}
      onAttachmentUploadStarted={mockOnAttachmentUploadStarted}
      onAttachmentUploadFinished={mockOnAttachmentUploadFinished}
    />
  )

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

  expect(mockUploadAttachment).toHaveBeenCalledTimes(1)
  expect(mockUploadAttachment).toHaveBeenCalledWith(mockFile, undefined)
  expect(mockOnAttachmentUploadStarted).toHaveBeenCalledTimes(1)
  expect(mockOnAttachmentUploadStarted).toHaveBeenCalledWith(mockFile)
  await waitFor(() => expect(mockOnAttachmentUploadFinished).toHaveBeenCalledTimes(1))
  expect(mockOnAttachmentUploadFinished).toHaveBeenCalledWith(mockFile, 'test-file-id')
  expect(fileInput).not.toHaveValue()
})

test('on unsuccessful file upload, onAttachmentUploadStarted and onAttachmentUploadFailed are called', async () => {
  const mockUploadAttachment = jest.fn().mockResolvedValue(undefined)
  const mockOnAttachmentUploadStarted = jest.fn()
  const mockOnAttachmentUploadFinished = jest.fn()
  const mockOnAttachmentUploadFailed = jest.fn()
  render(
    <AttachmentUploader
      uploadAttachment={mockUploadAttachment}
      onAttachmentUploadStarted={mockOnAttachmentUploadStarted}
      onAttachmentUploadFinished={mockOnAttachmentUploadFinished}
      onAttachmentUploadFailed={mockOnAttachmentUploadFailed}
    />
  )

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

  expect(mockUploadAttachment).toHaveBeenCalledTimes(1)
  expect(mockUploadAttachment).toHaveBeenCalledWith(mockFile, undefined)
  expect(mockOnAttachmentUploadStarted).toHaveBeenCalledTimes(1)
  expect(mockOnAttachmentUploadStarted).toHaveBeenCalledWith(mockFile)
  await waitFor(() => expect(mockOnAttachmentUploadFailed).toHaveBeenCalledTimes(1))
  expect(mockOnAttachmentUploadFailed).toHaveBeenCalledWith(mockFile)
  expect(mockOnAttachmentUploadFinished).not.toHaveBeenCalled()
  expect(fileInput).not.toHaveValue()
})

test('on failed file upload, onAttachmentUploadStarted and onAttachmentUploadFailed are called', async () => {
  const mockUploadAttachment = jest.fn().mockRejectedValue('')
  const mockOnAttachmentUploadStarted = jest.fn()
  const mockOnAttachmentUploadFailed = jest.fn()
  render(
    <AttachmentUploader
      uploadAttachment={mockUploadAttachment}
      onAttachmentUploadStarted={mockOnAttachmentUploadStarted}
      onAttachmentUploadFailed={mockOnAttachmentUploadFailed}
    />
  )

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

  expect(mockUploadAttachment).toHaveBeenCalledTimes(1)
  expect(mockUploadAttachment).toHaveBeenCalledWith(mockFile, undefined)
  expect(mockOnAttachmentUploadStarted).toHaveBeenCalledTimes(1)
  expect(mockOnAttachmentUploadStarted).toHaveBeenCalledWith(mockFile)
  await waitFor(() => expect(mockOnAttachmentUploadFailed).toHaveBeenCalledTimes(1))
  expect(mockOnAttachmentUploadFailed).toHaveBeenCalledWith(mockFile)
  expect(fileInput).not.toHaveValue()
})

test('when there is a recipient participant, uploadAttachment is called with them', async () => {
  const mockUploadAttachment = jest.fn().mockResolvedValue('test-file-id')
  const mockOnAttachmentUploadFinished = jest.fn()
  const mockRecipient = mock<ParticipantModel>({ id: 'testRemoteParticipantId' })
  render(
    <AttachmentUploader
      attachmentRecipient={mockRecipient}
      uploadAttachment={mockUploadAttachment}
      onAttachmentUploadFinished={mockOnAttachmentUploadFinished}
    />
  )

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

  await waitFor(() => expect(mockOnAttachmentUploadFinished).toHaveBeenCalledTimes(1))
  expect(mockUploadAttachment).toHaveBeenCalledTimes(1)
  expect(mockUploadAttachment).toHaveBeenCalledWith(mockFile, mockRecipient)
})

test('when no file is uploaded, upload handlers are not called', () => {
  const mockUploadAttachment = jest.fn()
  const mockOnAttachmentUploadStarted = jest.fn()
  render(
    <AttachmentUploader
      uploadAttachment={mockUploadAttachment}
      onAttachmentUploadStarted={mockOnAttachmentUploadStarted}
    />
  )

  const fileInput = screen.getByTestId('hidden-file-input')
  fireEvent.change(fileInput, { target: { value: [] } })

  expect(mockUploadAttachment).not.toHaveBeenCalled()
  expect(mockOnAttachmentUploadStarted).not.toHaveBeenCalled()
  expect(fileInput).not.toHaveValue()
})

test('renders a custom button element when a custom upload button element is given', () => {
  const DummyAttachmentUploadButton = () => <button>Dummy Button</button>
  render(
    <AttachmentUploader uploadAttachment={jest.fn()} components={{ UploadAttachment: DummyAttachmentUploadButton }} />
  )

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

const testAttachment: ChatAttachmentModel = {
  fileId: 'test-file-id',
  fileName: 'test-file-name.jpg',
  contentType: 'image/jpeg',
  fileSize: 5,
}

test('renders the attachment name and a button to clear the attachment', () => {
  render(<AttachmentDisplay attachment={testAttachment} onClearAttachment={jest.fn()} />)

  expect(screen.getByText('test-file-name.jpg')).toBeInTheDocument()
  expect(screen.getByRole('button', { name: 'Remove attachment' })).toBeInTheDocument()
})

test('calls the onClear callback when clicking on the clear button', () => {
  const mockOnClear = jest.fn()
  render(<AttachmentDisplay attachment={testAttachment} onClearAttachment={mockOnClear} />)

  userEvent.click(screen.getByRole('button', { name: 'Remove attachment' }))

  expect(mockOnClear).toHaveBeenCalledTimes(1)
})

test('renders an error when attachment upload has failed', () => {
  render(<AttachmentDisplay hasAttachmentError={true} />)

  expect(screen.getByText('Error while uploading attachment.')).toBeInTheDocument()
})

test('renders nothing if there is neither attachment nor error', () => {
  const { container } = render(<AttachmentDisplay />)

  expect(container.firstChild).toBeNull()
})

test('renders a custom clear button element when a custom clear button is given', () => {
  const DummyClearAttachmentButton = () => <button>Dummy Button</button>
  render(
    <AttachmentDisplay
      attachment={testAttachment}
      onClearAttachment={jest.fn()}
      components={{ ClearAttachmentButton: DummyClearAttachmentButton }}
    />
  )

  expect(screen.getByRole('button', { name: 'Dummy Button' })).toBeInTheDocument()
  expect(screen.queryByRole('button', { name: 'Remove attachment' })).not.toBeInTheDocument()
})
