import faker from 'faker';
import { keyBy, omit, orderBy } from 'lodash';
import { reset, verify, when } from 'testdouble';
import { URL } from 'url';

import { linkedinApiUrl } from '../../config';
import { defaultMocks } from '../utils/defaultMocks';
import { createGetMessagesResponse, createSendMessageResponse } from './message-factories';

const conversationId = faker.datatype.uuid();
const requestUrl = new URL(`messaging/conversations/${conversationId}/events`, linkedinApiUrl).toString();
const username = 'username';
const password = 'password';
let axios: any;
let Client: any;

beforeEach(async () => {
  ({ axios, Client } = await defaultMocks());
});

afterEach(() => {
  reset();
});

describe('getMessages', () => {
  const reqParams = {
    keyVersion: 'LEGACY_INBOX',
  };

  it('should fetch first page of messages', async () => {
    const { response, resultMessages } = createGetMessagesResponse(10);
    const keyedConversations = keyBy(resultMessages, 'entityUrn');

    when(axios.get(requestUrl, { params: reqParams })).thenResolve({ data: response });

    const client = await new Client().login.userPass({ username, password });
    const messagesScroller = client.message.getMessages({ conversationId });
    const messages = await messagesScroller.scrollNext();

    expect(messages.length).toEqual(10);
    messages.forEach((message: any) => {
      expect(omit(message, ['text', 'sentFrom'])).toEqual(keyedConversations[message.entityUrn]);
    });
  });

  it('should sort messages by createdAt descending', async () => {
    const { response } = createGetMessagesResponse(10);

    when(axios.get(requestUrl, { params: reqParams })).thenResolve({ data: response });

    const client = await new Client().login.userPass({ username, password });
    const messagesScroller = client.message.getMessages({ conversationId });
    const messages = await messagesScroller.scrollNext();

    expect(messages.length).toEqual(10);
    expect(messages).toEqual(orderBy(messages, 'createdAt', 'desc'));
  });

  it('should fetch first page of messages before specific date', async () => {
    const now = new Date();
    const { response, resultMessages } = createGetMessagesResponse(10);
    const keyedConversations = keyBy(resultMessages, 'entityUrn');

    when(axios.get(requestUrl, { params: { ...reqParams, createdBefore: now.getTime() } })).thenResolve({ data: response });

    const client = await new Client().login.userPass({ username, password });
    const messagesScroller = client.message.getMessages({ conversationId, createdBefore: now });
    const messages = await messagesScroller.scrollNext();

    expect(messages.length).toEqual(10);
    messages.forEach((message: any) => {
      expect(omit(message, ['text', 'sentFrom'])).toEqual(keyedConversations[message.entityUrn]);
    });
  });

  it('should add the text message in the root of the result ', async () => {
    const { response, resultMessages } = createGetMessagesResponse(1);
    const mockedResultMessage = resultMessages[0];

    when(axios.get(requestUrl, { params: reqParams })).thenResolve({ data: response });

    const client = await new Client().login.userPass({ username, password });
    const messagesScroller = client.message.getMessages({ conversationId });
    const messages = await messagesScroller.scrollNext();

    expect(messages.length).toEqual(1);
    expect(messages[0].text).toEqual(mockedResultMessage.eventContent?.attributedBody.text);
  });

  it('should add sentFrom on the result', async () => {
    const { response, resultMessages, resultProfiles } = createGetMessagesResponse(1);
    const profileId = resultProfiles[0].entityUrn.replace('urn:li:fs_miniProfile:', '');

    const participantId = `urn:li:fs_messagingMember:(${faker.datatype.number()},${profileId})`;
    resultMessages[0]['*from'] = participantId;

    when(axios.get(requestUrl, { params: reqParams })).thenResolve({ data: response });

    const client = await new Client().login.userPass({ username, password });
    const messagesScroller = client.message.getMessages({ conversationId });
    const messages = await messagesScroller.scrollNext();

    expect(messages[0].sentFrom.entityUrn).toEqual(resultProfiles[0].entityUrn);
    expect(messages[0].sentFrom.profileId).toEqual(profileId);
  });

  it('should be able to scroll messages using scroller', async () => {
    const { response: firstPageResponse, resultMessages: firstPageMockedMessages } = createGetMessagesResponse(10);
    const { response: secondPageResponse, resultMessages: secondPageMockedMessages } = createGetMessagesResponse(10);
    const { response: thirdPageResponse } = createGetMessagesResponse(10);

    when(axios.get(requestUrl, { params: reqParams })).thenResolve({
      data: firstPageResponse,
    });
    when(axios.get(requestUrl, { params: { ...reqParams, createdBefore: firstPageMockedMessages[9].createdAt } })).thenResolve({
      data: secondPageResponse,
    });
    when(axios.get(requestUrl, { params: { ...reqParams, createdBefore: secondPageMockedMessages[9].createdAt } })).thenResolve({
      data: thirdPageResponse,
    });

    const client = await new Client().login.userPass({ username, password });
    const messagesScroller = client.message.getMessages({ conversationId });
    const firstPageMessages = await messagesScroller.scrollNext();
    const secondPageMessages = await messagesScroller.scrollNext();
    const thirdPageMessages = await messagesScroller.scrollNext();

    expect(firstPageMessages.length).toEqual(10);
    expect(secondPageMessages.length).toEqual(10);
    expect(thirdPageMessages.length).toEqual(10);

    expect(firstPageMessages).not.toEqual(secondPageMessages);
    expect(firstPageMessages).not.toEqual(thirdPageMessages);
    expect(secondPageMessages).not.toEqual(thirdPageMessages);

    [...firstPageMessages, ...firstPageMessages, ...thirdPageMessages].forEach((p: any) => {
      expect(p.$type).toEqual('com.linkedin.voyager.messaging.Event');
    });
  });

  it('should be able to restart scroller position', async () => {
    const { response: firstPageResponse } = createGetMessagesResponse(10);

    when(axios.get(requestUrl, { params: reqParams })).thenResolve({
      data: firstPageResponse,
    });

    const client = await new Client().login.userPass({ username, password });
    const messagesScroller = client.message.getMessages({ conversationId });

    const firstPageMessages = await messagesScroller.scrollNext();

    messagesScroller.restart();

    const secondPageMessages = await messagesScroller.scrollNext();

    expect(firstPageMessages.length).toEqual(10);
    expect(firstPageMessages).toEqual(secondPageMessages);

    firstPageMessages.forEach((p: any) => {
      expect(p.$type).toEqual('com.linkedin.voyager.messaging.Event');
    });
  });

  it('should be able to override scroller starting point', async () => {
    const createdBefore = new Date();

    const { response: firstPageResponse, resultMessages: firstPageMockedMessages } = createGetMessagesResponse(10);
    const { response: secondPageResponse, resultMessages: secondPageMockedMessages } = createGetMessagesResponse(10);
    const { response: thirdPageResponse } = createGetMessagesResponse(10);

    when(axios.get(requestUrl, { params: { ...reqParams, createdBefore: createdBefore.getTime() } })).thenResolve({
      data: firstPageResponse,
    });
    when(axios.get(requestUrl, { params: { ...reqParams, createdBefore: firstPageMockedMessages[9].createdAt } })).thenResolve({
      data: secondPageResponse,
    });
    when(axios.get(requestUrl, { params: { ...reqParams, createdBefore: secondPageMockedMessages[9].createdAt } })).thenResolve({
      data: thirdPageResponse,
    });

    const client = await new Client().login.userPass({ username, password });
    const messagesScroller = client.message.getMessages({ conversationId, createdBefore });
    const firstPageMessages = await messagesScroller.scrollNext();
    const secondPageMessages = await messagesScroller.scrollNext();
    const thirdPageMessages = await messagesScroller.scrollNext();

    expect(firstPageMessages.length).toEqual(10);
    expect(secondPageMessages.length).toEqual(10);
    expect(thirdPageMessages.length).toEqual(10);

    expect(firstPageMessages).not.toEqual(secondPageMessages);
    expect(firstPageMessages).not.toEqual(thirdPageMessages);
    expect(secondPageMessages).not.toEqual(thirdPageMessages);

    [...firstPageMessages, ...firstPageMessages, ...thirdPageMessages].forEach((p: any) => {
      expect(p.$type).toEqual('com.linkedin.voyager.messaging.Event');
    });
  });

  it('should be able to scroll to previous response pages', async () => {
    const { response: firstPageResponse, resultMessages: firstPageMockedMessages } = createGetMessagesResponse(10);
    const { response: secondPageResponse, resultMessages: secondPageMockedMessages } = createGetMessagesResponse(10);
    const { response: thirdPageResponse } = createGetMessagesResponse(10);

    when(axios.get(requestUrl, { params: reqParams })).thenResolve({
      data: firstPageResponse,
    });
    when(
      axios.get(requestUrl, { params: { ...reqParams, createdBefore: firstPageMockedMessages[0].createdAt! + 1000 } }),
    ).thenResolve({
      data: firstPageResponse,
    });
    when(axios.get(requestUrl, { params: { ...reqParams, createdBefore: firstPageMockedMessages[9].createdAt } }), {
      times: 2,
    }).thenResolve({
      data: secondPageResponse,
    });
    when(axios.get(requestUrl, { params: { ...reqParams, createdBefore: secondPageMockedMessages[9].createdAt } })).thenResolve({
      data: thirdPageResponse,
    });

    const client = await new Client().login.userPass({ username, password });
    const messagesScroller = client.message.getMessages({ conversationId });
    const firstPageMessages = await messagesScroller.scrollNext();
    const secondPageMessages = await messagesScroller.scrollNext();
    const thirdPageMessages = await messagesScroller.scrollNext();
    const fourthPageMessages = await messagesScroller.scrollBack();
    const fifthPageMessages = await messagesScroller.scrollBack();

    expect(firstPageMessages.length).toEqual(10);
    expect(secondPageMessages.length).toEqual(10);
    expect(thirdPageMessages.length).toEqual(10);

    expect(firstPageMessages).not.toEqual(secondPageMessages);
    expect(firstPageMessages).not.toEqual(thirdPageMessages);
    expect(secondPageMessages).not.toEqual(thirdPageMessages);
    expect(fourthPageMessages).toEqual(secondPageMessages);
    expect(fifthPageMessages).toEqual(firstPageMessages);

    [...firstPageMessages, ...firstPageMessages, ...thirdPageMessages].forEach((p: any) => {
      expect(p.$type).toEqual('com.linkedin.voyager.messaging.Event');
    });
  });

  it('should be return empty array if trying to scroll back from the starting point', async () => {
    const { response: firstPageResponse } = createGetMessagesResponse(10);

    when(axios.get(requestUrl, { params: reqParams })).thenResolve({
      data: firstPageResponse,
    });

    const client = await new Client().login.userPass({ username, password });
    const messagesScroller = client.message.getMessages({ conversationId });
    await messagesScroller.scrollNext();
    const messages = await messagesScroller.scrollBack();

    expect(messages).toEqual([]);
  });
});

describe('sendMessage', () => {
  const sendMessageRequestUrl = new URL('messaging/conversations', linkedinApiUrl).toString();
  const profileId = faker.datatype.uuid();
  const text = faker.lorem.sentence();
  const queryParams = {
    action: 'create',
  };
  const payload = {
    keyVersion: 'LEGACY_INBOX',
    conversationCreate: {
      eventCreate: {
        value: {
          'com.linkedin.voyager.messaging.create.MessageCreate': {
            attributedBody: {
              text,
              attributes: [],
            },
            attachments: [],
          },
        },
      },
      subtype: 'MEMBER_TO_MEMBER',
      recipients: [profileId],
    },
  };

  beforeEach(() => {});

  it('should send message to the correct profileId', async () => {
    const { response, resultCreationEvent } = createSendMessageResponse();

    when(axios.post(sendMessageRequestUrl, payload, { params: queryParams })).thenResolve({
      data: response,
    });

    const client = await new Client().login.userPass({ username, password });
    const createdMessage = await client.message.sendMessage({ profileId, text });

    expect(omit(createdMessage, 'text')).toEqual(resultCreationEvent);
  });

  it('should add text to the send message response', async () => {
    const { response } = createSendMessageResponse();

    when(axios.post(sendMessageRequestUrl, payload, { params: queryParams })).thenResolve({
      data: response,
    });

    const client = await new Client().login.userPass({ username, password });
    const createdMessage = await client.message.sendMessage({ profileId, text });

    expect(createdMessage.text).toEqual(text);
  });
});