import { act, render, screen } from '@testing-library/react'
import { configureStore } from '@reduxjs/toolkit'
import { Provider } from 'react-redux'
import { ChatInputProps, ChatMessageProps } from './models'
import React, { ComponentType } from 'react'
import { zocksStoreConfig } from '../../app/store'
import { addRemoteParticipant, setLocalParticipantId, setSessionState } from '../../store/session/sessionSlice'
import {
  privateChatMessageReceived,
  privateChatMessageSent,
  publicChatMessageReceived,
} from '../../store/chat/chatSlice'
import { SplitChat } from './SplitChat'
import userEvent from '@testing-library/user-event'
import { ChatMessageAction } from '../../store/chat/actionModels'
import { SessionState } from '@zocks-communications/client-sdk-js'
import { StyledComponent } from '../common'

beforeEach(() => {
  window.HTMLElement.prototype.scrollIntoView = function () {}
})

const testPublicMessages: ChatMessageAction[] = [
  {
    message: 'test public message 1',
    from: { id: 'testLocalParticipantId', name: 'Test Local Participant Name' },
    timestamp: 1,
  },
  {
    message: 'test public message 2',
    from: { id: 'testLocalParticipantId', name: 'Test Local Participant Name' },
    timestamp: 2,
  },
]

const testReceivedPrivateMessages: ChatMessageAction[] = [
  {
    message: 'test received private message 1',
    from: { id: 'testRemoteParticipantId', name: 'Test Remote Participant Name' },
    to: { id: 'testLocalParticipantId', name: 'Test Local Participant Name' },
    timestamp: 3,
  },
  {
    message: 'test received private message 2',
    from: { id: 'testRemoteParticipantId', name: 'Test Remote Participant Name' },
    to: { id: 'testLocalParticipantId', name: 'Test Local Participant Name' },
    timestamp: 4,
  },
]

const testSentPrivateMessages: ChatMessageAction[] = [
  {
    message: 'test sent private message 1',
    from: { id: 'testLocalParticipantId', name: 'Test Local Participant Name' },
    to: { id: 'testRemoteParticipantId', name: 'Test Remote Participant Name' },
    timestamp: 5,
  },
  {
    message: 'test sent private message 2',
    from: { id: 'testLocalParticipantId', name: 'Test Local Participant Name' },
    to: { id: 'testRemoteParticipantId', name: 'Test Remote Participant Name' },
    timestamp: 6,
  },
]

interface ChatRenderProps {
  receivedPrivateMessages?: ChatMessageAction[]
  sentPrivateMessages?: ChatMessageAction[]
  publicMessages?: ChatMessageAction[]
  onNewChatMessage?: () => void
  components?: {
    ChatMessage?: ComponentType<ChatMessageProps>
    ChatInput?: ComponentType<ChatInputProps>
    ParticipantNotConnected?: ComponentType<StyledComponent>
  }
}

const renderWithState = ({
  receivedPrivateMessages,
  sentPrivateMessages,
  publicMessages,
  components,
}: ChatRenderProps = {}) => {
  const mockChatActions = {
    sendPublicMessage: jest.fn(),
    sendPrivateMessage: jest.fn(),
    uploadAttachment: jest.fn(),
    getAttachmentLocation: jest.fn(),
  }
  const mockDismiss = jest.fn()
  const store = configureStore(zocksStoreConfig)
  store.dispatch(setSessionState(SessionState.CONNECTED))
  store.dispatch(setLocalParticipantId('testLocalParticipantId'))
  store.dispatch(
    addRemoteParticipant({
      participantId: 'testRemoteParticipantId',
      participantName: 'Test Remote Participant Name',
    })
  )
  publicMessages?.forEach((message) => store.dispatch(publicChatMessageReceived(message)))
  receivedPrivateMessages?.forEach((message) => store.dispatch(privateChatMessageReceived(message)))
  sentPrivateMessages?.forEach((message) => store.dispatch(privateChatMessageSent(message)))
  const { container } = render(
    <Provider store={store}>
      <SplitChat chatActions={mockChatActions} dismiss={mockDismiss} components={components} />
    </Provider>
  )

  return { container, store, mockChatActions, mockDismiss }
}

test('displays only public chat messages when public messages are selected', () => {
  const { container } = renderWithState({
    publicMessages: testPublicMessages,
    receivedPrivateMessages: testReceivedPrivateMessages,
    sentPrivateMessages: testSentPrivateMessages,
  })

  const chatMessages = container.querySelectorAll('.zocks__chat-message-a__message')
  expect(chatMessages.length).toBe(2)
  expect(chatMessages[0]).toHaveTextContent('test public message 1')
  expect(chatMessages[1]).toHaveTextContent('test public message 2')
})

test('displays only private chat messages when private chat messages are selected', () => {
  const { container } = renderWithState({
    publicMessages: testPublicMessages,
    receivedPrivateMessages: testReceivedPrivateMessages,
    sentPrivateMessages: testSentPrivateMessages,
  })

  userEvent.selectOptions(screen.getByRole('combobox'), 'testRemoteParticipantId')

  const chatMessages = container.querySelectorAll('.zocks__chat-message-a__message')
  expect(chatMessages.length).toBe(4)
  expect(chatMessages[0]).toHaveTextContent('test received private message 1')
  expect(chatMessages[1]).toHaveTextContent('test received private message 2')
  expect(chatMessages[2]).toHaveTextContent('test sent private message 1')
  expect(chatMessages[3]).toHaveTextContent('test sent private message 2')
})

test('displays no chat messages when private messages are selected but there were no previous private messages', () => {
  const { container } = renderWithState()

  userEvent.selectOptions(screen.getByRole('combobox'), 'testRemoteParticipantId')

  const chatMessages = container.querySelectorAll('.zocks__chat-message-a__message')
  expect(chatMessages.length).toBe(0)
})

test('renders a not connected placeholder when the local participant is not connected', () => {
  const { store } = renderWithState()
  act(() => {
    store.dispatch(setSessionState(SessionState.DISCONNECTED))
  })

  expect(screen.getByText('Please connect to a room in order to use the chat.')).toBeInTheDocument()
})

test('renders a custom chat input element if a custom input element is given', () => {
  const DummyChatInputElement = () => <div data-testid='dummy-chat-input' />
  renderWithState({ components: { ChatInput: DummyChatInputElement } })

  expect(screen.getByTestId('dummy-chat-input')).toBeInTheDocument()
  expect(screen.queryByRole('textbox')).not.toBeInTheDocument()
  expect(screen.queryByRole('button', { name: 'send' })).not.toBeInTheDocument()
})

test('renders a custom chat message element if a custom message element is given', () => {
  const DummyChatMessageElement = () => <div data-testid='dummy-chat-message' />
  renderWithState({ publicMessages: testPublicMessages, components: { ChatMessage: DummyChatMessageElement } })

  expect(screen.getAllByTestId('dummy-chat-message')).toHaveLength(2)
  expect(screen.queryByText('test public message 1')).not.toBeInTheDocument()
  expect(screen.queryByText('test public message 2')).not.toBeInTheDocument()
})

test('renders a custom not connected placeholder if a custom placeholder element is given', () => {
  const DummyNotConnected = () => <div>Dummy not connected placeholder.</div>
  const { store } = renderWithState({ components: { ParticipantNotConnected: DummyNotConnected } })
  act(() => {
    store.dispatch(setSessionState(SessionState.DISCONNECTED))
  })

  expect(screen.getByText('Dummy not connected placeholder.')).toBeInTheDocument()
})
