import chatReducer, {
  clearChatMessages,
  clearNewMessageFrom,
  privateChatMessageReceived,
  privateChatMessageSent,
  publicChatMessageReceived,
  publicChatMessageSent,
  setMessageRecipient,
} from './chatSlice'
import { ChatMessageType, ChatModel } from './models'
import { PUBLIC_CHAT_RECIPIENT } from './constants'

const state: ChatModel = {
  publicChatMessages: [],
  privateChatMessages: {},
  messageRecipient: PUBLIC_CHAT_RECIPIENT,
  newMessagesFrom: [],
}

test('chat has an initial state', () => {
  expect(chatReducer(undefined, { type: 'unknown' })).toStrictEqual(state)
})

test('saves a public chat message and adds Everyone to new message senders when receiving a public message', () => {
  const testPublicChatMessage = {
    message: 'test public message',
    timestamp: 1,
    from: {
      id: 'testParticipantId',
      name: 'Test Participant name',
    },
  }

  expect(chatReducer(state, publicChatMessageReceived(testPublicChatMessage))).toStrictEqual({
    publicChatMessages: [{ ...testPublicChatMessage, type: ChatMessageType.PUBLIC }],
    privateChatMessages: {},
    messageRecipient: PUBLIC_CHAT_RECIPIENT,
    newMessagesFrom: [PUBLIC_CHAT_RECIPIENT],
  })
})

test('saves a public chat message but does not add Everyone to new message senders when sending a public message', () => {
  const testPublicChatMessage = {
    message: 'test public message',
    timestamp: 1,
    from: {
      id: 'testLocalParticipantId',
      name: 'Test Local Participant name',
    },
  }

  expect(chatReducer(state, publicChatMessageSent(testPublicChatMessage))).toStrictEqual({
    publicChatMessages: [{ ...testPublicChatMessage, type: ChatMessageType.PUBLIC }],
    privateChatMessages: {},
    messageRecipient: '',
    newMessagesFrom: [],
  })
})

test('saves a private chat message sent by a remote participant, and adds their id to new message senders', () => {
  const testPrivateChatMessage = {
    message: 'test private message for me',
    timestamp: 1,
    from: {
      id: 'testParticipantId',
      name: 'Test Participant name',
    },
    to: {
      id: 'testLocalParticipantId',
      name: 'Test Local Participant Name',
    },
  }

  expect(chatReducer(state, privateChatMessageReceived(testPrivateChatMessage))).toStrictEqual({
    publicChatMessages: [],
    privateChatMessages: {
      testParticipantId: [{ ...testPrivateChatMessage, type: ChatMessageType.PRIVATE }],
    },
    messageRecipient: PUBLIC_CHAT_RECIPIENT,
    newMessagesFrom: ['testParticipantId'],
  })
})

test('appends new chat messages from a remote participant to existing messages', () => {
  const testPrivateChatMessage = {
    message: 'test private message for me',
    timestamp: 1,
    from: {
      id: 'testParticipantId',
      name: 'Test Participant name',
    },
    to: {
      id: 'testLocalParticipantId',
      name: 'Test Local Participant Name',
    },
  }
  const testExistingPrivateMessage = {
    type: ChatMessageType.PRIVATE,
    message: 'old chat message',
    timestamp: 1,
    from: {
      id: 'testLocalParticipantId',
      name: 'Test Local Participant Name',
    },
  }

  expect(
    chatReducer(
      {
        ...state,
        privateChatMessages: {
          testParticipantId: [testExistingPrivateMessage],
        },
      },
      privateChatMessageReceived(testPrivateChatMessage)
    )
  ).toStrictEqual({
    publicChatMessages: [],
    privateChatMessages: {
      testParticipantId: [testExistingPrivateMessage, { ...testPrivateChatMessage, type: ChatMessageType.PRIVATE }],
    },
    messageRecipient: PUBLIC_CHAT_RECIPIENT,
    newMessagesFrom: ['testParticipantId'],
  })
})

test('saves a private chat message sent by the local participant', () => {
  const testPrivateChatMessage = {
    message: 'test private message from me',
    timestamp: 1,
    from: {
      id: 'testLocalParticipantId',
      name: 'Test Local Participant Name',
    },
    to: {
      id: 'testParticipantId',
      name: 'Test Participant name',
    },
  }

  expect(chatReducer(state, privateChatMessageSent(testPrivateChatMessage))).toStrictEqual({
    publicChatMessages: [],
    privateChatMessages: {
      testParticipantId: [{ ...testPrivateChatMessage, type: ChatMessageType.PRIVATE }],
    },
    messageRecipient: PUBLIC_CHAT_RECIPIENT,
    newMessagesFrom: [],
  })
})

test('clears all chat messages', () => {
  const stateWithMessages: ChatModel = {
    publicChatMessages: [
      {
        type: ChatMessageType.PUBLIC,
        message: 'test public message',
        timestamp: 1,
        from: {
          id: 'testParticipantId',
          name: 'Test Participant name',
        },
      },
    ],
    privateChatMessages: {
      testParticipantId: [
        {
          type: ChatMessageType.PRIVATE,
          message: 'test private message for me',
          timestamp: 1,
          from: {
            id: 'testParticipantId',
            name: 'Test Participant name',
          },
          to: {
            id: 'testLocalParticipantId',
            name: 'Test Local Participant Name',
          },
        },
      ],
    },
    messageRecipient: 'testRemoteParticipantId1',
    newMessagesFrom: [PUBLIC_CHAT_RECIPIENT, 'testRemoteParticipantId12'],
  }

  expect(chatReducer(stateWithMessages, clearChatMessages())).toStrictEqual({
    publicChatMessages: [],
    privateChatMessages: {},
    messageRecipient: PUBLIC_CHAT_RECIPIENT,
    newMessagesFrom: [],
  })
})

test('does not save chat message from an unknown remote participant', () => {
  const testPrivateChatMessageWithoutSender = {
    message: 'test private message for me',
    timestamp: 1,
    from: {
      id: '',
      name: '',
    },
    to: {
      id: 'testLocalParticipantId',
      name: 'Test Local Participant Name',
    },
  }

  expect(chatReducer(state, privateChatMessageReceived(testPrivateChatMessageWithoutSender))).toStrictEqual(state)
})

test('does not save chat message for an unknown remote participant', () => {
  const testPrivateChatMessageWithoutRecipient = {
    message: 'test private message for me',
    timestamp: 1,
    from: {
      id: 'testLocalParticipantId',
      name: 'Test Local Participant Name',
    },
  }

  expect(chatReducer(state, privateChatMessageSent(testPrivateChatMessageWithoutRecipient))).toStrictEqual(state)
})

test('sets the message recipient', () => {
  expect(chatReducer(state, setMessageRecipient('testRemoteParticipantId1'))).toStrictEqual({
    ...state,
    messageRecipient: 'testRemoteParticipantId1',
  })
})

test('removes the public recipient as the message sender from new message senders', () => {
  expect(
    chatReducer(
      { ...state, newMessagesFrom: [PUBLIC_CHAT_RECIPIENT, 'testRemoteParticipantId'] },
      clearNewMessageFrom(PUBLIC_CHAT_RECIPIENT)
    )
  ).toStrictEqual({ ...state, newMessagesFrom: ['testRemoteParticipantId'] })
})

test('removes a specific participant as the message sender from new message senders', () => {
  expect(
    chatReducer(
      { ...state, newMessagesFrom: [PUBLIC_CHAT_RECIPIENT, 'testRemoteParticipantId'] },
      clearNewMessageFrom('testRemoteParticipantId')
    )
  ).toStrictEqual({ ...state, newMessagesFrom: [PUBLIC_CHAT_RECIPIENT] })
})
