module.exports = {
  inputs: {
    record: {
      type: 'ref',
      required: true,
    },
    toBoard: {
      type: 'ref',
    },
    toList: {
      type: 'ref',
    },
    values: {
      type: 'json',
      custom: (value) => {
        if (!_.isPlainObject(value)) {
          return false;
        }

        if (!_.isUndefined(value.position) && !_.isFinite(value.position)) {
          return false;
        }

        return true;
      },
      required: true,
    },
    board: {
      type: 'ref',
    },
    list: {
      type: 'ref',
    },
    user: {
      type: 'ref',
    },
    request: {
      type: 'ref',
    },
  },

  exits: {
    boardMustBePresent: {},
    listMustBePresent: {},
    toListMustBelongToBoard: {},
    toListMustBePresent: {},
    positionMustBeInValues: {},
    userMustBePresent: {},
  },

  async fn(inputs, exits) {
    const { isSubscribed, ...values } = inputs.values;

    if (inputs.toBoard || inputs.toList || !_.isUndefined(values.position)) {
      if (!inputs.board) {
        throw 'boardMustBePresent';
      }

      if (inputs.toBoard) {
        if (inputs.toBoard.id === inputs.board.id) {
          delete inputs.toBoard; // eslint-disable-line no-param-reassign
        } else {
          values.boardId = inputs.toBoard.id;
        }
      }

      const board = inputs.toBoard || inputs.board;

      if (inputs.toList) {
        if (inputs.board.type === 'kanban' && !inputs.list) {
          throw 'listMustBePresent';
        }

        if (inputs.toList.boardId !== board.id) {
          throw 'toListMustBelongToBoard';
        }

        if (
          board.type === 'collection' ||
          (inputs.board.type === 'kanban' && inputs.toList.id === inputs.list.id)
        ) {
          delete inputs.toList; // eslint-disable-line no-param-reassign
        } else {
          values.listId = inputs.toList.id;
        }
      }

      if (inputs.toList) {
        if (_.isUndefined(values.position)) {
          throw 'positionMustBeInValues';
        }
      } else if (inputs.toBoard) {
        if (inputs.toBoard.type === 'kanban') {
          throw 'toListMustBePresent';
        }

        if (inputs.board.type === 'kanban') {
          values.listId = null;
          values.position = null;
        }
      }
    }

    if ((!_.isUndefined(isSubscribed) || inputs.toBoard || inputs.toList) && !inputs.user) {
      throw 'userMustBePresent';
    }

    if (!_.isNil(values.position)) {
      const cards = await sails.helpers.getCardsForList(
        values.listId || inputs.record.listId,
        inputs.record.id,
      );

      const { position, repositions } = sails.helpers.insertToPositionables(values.position, cards);

      repositions.forEach(async ({ id, position: nextPosition }) => {
        await Card.update({
          id,
          listId,
        }).set({
          position: nextPosition,
        });

        sails.sockets.broadcast(`board:${values.boardId || inputs.record.boardId}`, 'cardUpdate', {
          item: {
            id,
            position: nextPosition,
          },
        });
      });

      values.position = position;
    }

    let card;
    if (!_.isEmpty(values)) {
      let prevLabels;
      if (inputs.toBoard) {
        if (inputs.toBoard.projectId !== inputs.board.projectId) {
          const userIds = await sails.helpers.getMembershipUserIdsForProject(
            inputs.toBoard.projectId,
          );

          await CardSubscription.destroy({
            cardId: inputs.record.id,
            userId: {
              '!=': userIds,
            },
          });

          await CardMembership.destroy({
            cardId: inputs.record.id,
            userId: {
              '!=': userIds,
            },
          });
        }

        prevLabels = await sails.helpers.getLabelsForCard(inputs.record.id);

        await CardLabel.destroy({
          cardId: inputs.record.id,
        });
      }

      card = await Card.updateOne(inputs.record.id).set(values);

      if (!card) {
        return exits.success(card);
      }

      if (inputs.toBoard) {
        sails.sockets.broadcast(
          `board:${inputs.board.id}`,
          'cardDelete',
          {
            item: inputs.record,
          },
          inputs.request,
        );

        const labels = await sails.helpers.getLabelsForBoard(card.boardId);
        const labelByNameMap = _.keyBy(labels, 'name');

        const labelIds = await Promise.all(
          prevLabels.map(async (prevLabel) => {
            if (labelByNameMap[prevLabel.name]) {
              return labelByNameMap[prevLabel.name].id;
            }

            const { id } = await sails.helpers.createLabel(
              inputs.toBoard,
              _.omit(prevLabel, ['id', 'boardId']),
            );

            return id;
          }),
        );

        await Promise.all(
          labelIds.map(async (labelId) => {
            await CardLabel.create({
              labelId,
              cardId: card.id,
            })
              .tolerate('E_UNIQUE')
              .fetch();
          }),
        );

        const cardMemberships = await sails.helpers.getMembershipsForCard(card.id);
        const cardLabels = await sails.helpers.getCardLabelsForCard(card.id);
        const tasks = await sails.helpers.getTasksForCard(card.id);
        const attachments = await sails.helpers.getAttachmentsForCard(card.id);

        sails.sockets.broadcast(`board:${card.boardId}`, 'cardCreate', {
          item: card,
          included: {
            cardMemberships,
            cardLabels,
            tasks,
            attachments,
          },
        });

        const userIds = await sails.helpers.getSubscriptionUserIdsForCard(card.id);

        userIds.forEach((userId) => {
          sails.sockets.broadcast(`user:${userId}`, 'cardUpdate', {
            item: {
              id: card.id,
              isSubscribed: true,
            },
          });
        });
      } else {
        sails.sockets.broadcast(
          `board:${card.boardId}`,
          'cardUpdate',
          {
            item: card,
          },
          inputs.request,
        );
      }

      if (!inputs.toBoard && inputs.toList) {
        // TODO: add transfer action
        await sails.helpers.createAction(card, inputs.user, {
          type: 'moveCard',
          data: {
            fromList: _.pick(inputs.list, ['id', 'name']),
            toList: _.pick(inputs.toList, ['id', 'name']),
          },
        });
      }
    } else {
      card = inputs.record;
    }

    if (!_.isUndefined(isSubscribed)) {
      const cardSubscription = await CardSubscription.findOne({
        cardId: card.id,
        userId: inputs.user.id,
      });

      if (isSubscribed !== !!cardSubscription) {
        if (isSubscribed) {
          await CardSubscription.create({
            cardId: card.id,
            userId: inputs.user.id,
          }).tolerate('E_UNIQUE');
        } else {
          await CardSubscription.destroyOne({
            cardId: card.id,
            userId: inputs.user.id,
          });
        }

        sails.sockets.broadcast(
          `user:${inputs.user.id}`,
          'cardUpdate',
          {
            item: {
              isSubscribed,
              id: card.id,
            },
          },
          inputs.request,
        );
      }
    }

    return exits.success(card);
  },
};
