/* global window, textsecure, dcodeIO, StringView, ConversationController */

// eslint-disable-next-line func-names
(function() {
  window.libloki = window.libloki || {};

  async function sendBackgroundMessage(pubKey) {
    return sendOnlineBroadcastMessage(pubKey);
  }

  async function sendOnlineBroadcastMessage(pubKey, isPing = false) {
    const authorisation = await window.libloki.storage.getGrantAuthorisationForSecondaryPubKey(
      pubKey
    );
    if (authorisation && authorisation.primaryDevicePubKey !== pubKey) {
      sendOnlineBroadcastMessage(authorisation.primaryDevicePubKey);
      return;
    }
    const p2pAddress = null;
    const p2pPort = null;
    // We result loki address message for sending "background" messages
    const type = textsecure.protobuf.LokiAddressMessage.Type.HOST_UNREACHABLE;

    const lokiAddressMessage = new textsecure.protobuf.LokiAddressMessage({
      p2pAddress,
      p2pPort,
      type,
    });
    const content = new textsecure.protobuf.Content({
      lokiAddressMessage,
    });

    const options = { messageType: 'onlineBroadcast', isPing };
    // Send a empty message with information about how to contact us directly
    const outgoingMessage = new textsecure.OutgoingMessage(
      null, // server
      Date.now(), // timestamp,
      [pubKey], // numbers
      content, // message
      true, // silent
      () => null, // callback
      options
    );
    await outgoingMessage.sendToNumber(pubKey);
  }

  function createPairingAuthorisationProtoMessage({
    primaryDevicePubKey,
    secondaryDevicePubKey,
    requestSignature,
    grantSignature,
  }) {
    if (!primaryDevicePubKey || !secondaryDevicePubKey || !requestSignature) {
      throw new Error(
        'createPairingAuthorisationProtoMessage: pubkeys missing'
      );
    }
    if (requestSignature.constructor !== ArrayBuffer) {
      throw new Error(
        'createPairingAuthorisationProtoMessage expects a signature as ArrayBuffer'
      );
    }
    if (grantSignature && grantSignature.constructor !== ArrayBuffer) {
      throw new Error(
        'createPairingAuthorisationProtoMessage expects a signature as ArrayBuffer'
      );
    }
    return new textsecure.protobuf.PairingAuthorisationMessage({
      requestSignature: new Uint8Array(requestSignature),
      grantSignature: grantSignature ? new Uint8Array(grantSignature) : null,
      primaryDevicePubKey,
      secondaryDevicePubKey,
    });
  }

  function sendUnpairingMessageToSecondary(pubKey) {
    const flags = textsecure.protobuf.DataMessage.Flags.UNPAIRING_REQUEST;
    const dataMessage = new textsecure.protobuf.DataMessage({
      flags,
    });
    const content = new textsecure.protobuf.Content({
      dataMessage,
    });
    const options = { messageType: 'device-unpairing' };
    const outgoingMessage = new textsecure.OutgoingMessage(
      null, // server
      Date.now(), // timestamp,
      [pubKey], // numbers
      content, // message
      true, // silent
      () => null, // callback
      options
    );
    return outgoingMessage.sendToNumber(pubKey);
  }
  // Serialise as <Element0.length><Element0><Element1.length><Element1>...
  // This is an implementation of the reciprocal of contacts_parser.js
  function serialiseByteBuffers(buffers) {
    const result = new dcodeIO.ByteBuffer();
    buffers.forEach(buffer => {
      // bytebuffer container expands and increments
      // offset automatically
      result.writeInt32(buffer.limit);
      result.append(buffer);
    });
    result.limit = result.offset;
    result.reset();
    return result;
  }
  async function createContactSyncProtoMessage(conversations) {
    // Extract required contacts information out of conversations
    const sessionContacts = conversations.filter(
      c => c.isPrivate() && !c.isSecondaryDevice() && c.isFriend()
    );

    if (sessionContacts.length === 0) {
      return null;
    }

    const rawContacts = await Promise.all(
      sessionContacts.map(async conversation => {
        const profile = conversation.getLokiProfile();
        const number = conversation.getNumber();
        const name = profile
          ? profile.displayName
          : conversation.getProfileName();
        const status = await conversation.safeGetVerified();
        const protoState = textsecure.storage.protocol.convertVerifiedStatusToProtoState(
          status
        );
        const verified = new textsecure.protobuf.Verified({
          state: protoState,
          destination: number,
          identityKey: StringView.hexToArrayBuffer(number),
        });
        return {
          name,
          verified,
          number,
          nickname: conversation.getNickname(),
          blocked: conversation.isBlocked(),
          expireTimer: conversation.get('expireTimer'),
        };
      })
    );
    // Convert raw contacts to an array of buffers
    const contactDetails = rawContacts
      .filter(x => x.number !== textsecure.storage.user.getNumber())
      .map(x => new textsecure.protobuf.ContactDetails(x))
      .map(x => x.encode());
    // Serialise array of byteBuffers into 1 byteBuffer
    const byteBuffer = serialiseByteBuffers(contactDetails);
    const data = new Uint8Array(byteBuffer.toArrayBuffer());
    const contacts = new textsecure.protobuf.SyncMessage.Contacts({
      data,
    });
    const syncMessage = new textsecure.protobuf.SyncMessage({
      contacts,
    });
    return syncMessage;
  }
  function createGroupSyncProtoMessage(conversations) {
    // We only want to sync across closed groups that we haven't left
    const sessionGroups = conversations.filter(
      c => c.isClosedGroup() && !c.get('left') && c.isFriend()
    );

    if (sessionGroups.length === 0) {
      return null;
    }

    const rawGroups = sessionGroups.map(conversation => ({
      id: window.Signal.Crypto.bytesFromString(conversation.id),
      name: conversation.get('name'),
      members: conversation.get('members') || [],
      blocked: conversation.isBlocked(),
      expireTimer: conversation.get('expireTimer'),
      admins: conversation.get('groupAdmins') || [],
    }));

    // Convert raw groups to an array of buffers
    const groupDetails = rawGroups
      .map(x => new textsecure.protobuf.GroupDetails(x))
      .map(x => x.encode());
    // Serialise array of byteBuffers into 1 byteBuffer
    const byteBuffer = serialiseByteBuffers(groupDetails);
    const data = new Uint8Array(byteBuffer.toArrayBuffer());
    const groups = new textsecure.protobuf.SyncMessage.Groups({
      data,
    });
    const syncMessage = new textsecure.protobuf.SyncMessage({
      groups,
    });
    return syncMessage;
  }
  function createOpenGroupsSyncProtoMessage(conversations) {
    // We only want to sync across open groups that we haven't left
    const sessionOpenGroups = conversations.filter(
      c => c.isPublic() && !c.isRss() && !c.get('left')
    );

    if (sessionOpenGroups.length === 0) {
      return null;
    }

    const openGroups = sessionOpenGroups.map(
      conversation =>
        new textsecure.protobuf.SyncMessage.OpenGroupDetails({
          url: conversation.id.split('@').pop(),
          channelId: conversation.get('channelId'),
        })
    );

    const syncMessage = new textsecure.protobuf.SyncMessage({
      openGroups,
    });
    return syncMessage;
  }
  async function sendPairingAuthorisation(authorisation, recipientPubKey) {
    const pairingAuthorisation = createPairingAuthorisationProtoMessage(
      authorisation
    );
    const ourNumber = textsecure.storage.user.getNumber();
    const ourConversation = await ConversationController.getOrCreateAndWait(
      ourNumber,
      'private'
    );
    const content = new textsecure.protobuf.Content({
      pairingAuthorisation,
    });
    const isGrant = authorisation.primaryDevicePubKey === ourNumber;
    if (isGrant) {
      // Send profile name to secondary device
      const lokiProfile = ourConversation.getLokiProfile();
      // profile.avatar is the path to the local image
      // replace with the avatar URL
      const avatarPointer = ourConversation.get('avatarPointer');
      lokiProfile.avatar = avatarPointer;
      const profile = new textsecure.protobuf.DataMessage.LokiProfile(
        lokiProfile
      );
      const profileKey = window.storage.get('profileKey');
      const dataMessage = new textsecure.protobuf.DataMessage({
        profile,
        profileKey,
      });
      content.dataMessage = dataMessage;
    }
    // Send
    const options = { messageType: 'pairing-request' };
    const p = new Promise((resolve, reject) => {
      const timestamp = Date.now();

      const outgoingMessage = new textsecure.OutgoingMessage(
        null, // server
        timestamp,
        [recipientPubKey], // numbers
        content, // message
        true, // silent
        result => {
          // callback
          if (result.errors.length > 0) {
            reject(result.errors[0]);
          } else {
            resolve();
          }
        },
        options
      );
      outgoingMessage.sendToNumber(recipientPubKey);
    });
    return p;
  }

  window.libloki.api = {
    sendBackgroundMessage,
    sendOnlineBroadcastMessage,
    sendPairingAuthorisation,
    createPairingAuthorisationProtoMessage,
    sendUnpairingMessageToSecondary,
    createContactSyncProtoMessage,
    createGroupSyncProtoMessage,
    createOpenGroupsSyncProtoMessage,
  };
})();
