"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.CaseCommentModel = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _boom = _interopRequireDefault(require("@hapi/boom"));
var _domain = require("../../../common/types/domain");
var _constants = require("../../../common/constants");
var _error = require("../error");
var _limiter_checker = require("../limiter_checker");
var _utils = require("../utils");
var _runtime_types = require("../../../common/api/runtime_types");
/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License
 * 2.0; you may not use this file except in compliance with the Elastic License
 * 2.0.
 */

/**
 * This class represents a case that can have a comment attached to it.
 */
class CaseCommentModel {
  constructor(caseInfo, params) {
    (0, _defineProperty2.default)(this, "params", void 0);
    (0, _defineProperty2.default)(this, "caseInfo", void 0);
    this.caseInfo = caseInfo;
    this.params = params;
  }
  static async create(id, options) {
    const savedObject = await options.services.caseService.getCase({
      id
    });
    return new CaseCommentModel(savedObject, options);
  }
  get savedObject() {
    return this.caseInfo;
  }

  /**
   * Update a comment and update the corresponding case's update_at and updated_by fields.
   */
  async updateComment({
    updateRequest,
    updatedAt,
    owner
  }) {
    try {
      const {
        id,
        version,
        ...queryRestAttributes
      } = updateRequest;
      const options = {
        version,
        /**
         * This is to handle a scenario where an update occurs for an attachment framework style comment.
         * The code that extracts the reference information from the attributes doesn't know about the reference to the case
         * and therefore will accidentally remove that reference and we'll lose the connection between the comment and the
         * case.
         */
        references: [...this.buildRefsToCase()],
        refresh: false
      };
      if (queryRestAttributes.type === _domain.AttachmentType.user && queryRestAttributes !== null && queryRestAttributes !== void 0 && queryRestAttributes.comment) {
        const currentComment = await this.params.services.attachmentService.getter.get({
          attachmentId: id
        });
        const updatedReferences = (0, _utils.getOrUpdateLensReferences)(this.params.lensEmbeddableFactory, queryRestAttributes.comment, currentComment);

        /**
         * The call to getOrUpdateLensReferences already handles retrieving the reference to the case and ensuring that is
         * also included here so it's ok to overwrite what was set before.
         */
        options.references = updatedReferences;
      }
      const [comment, commentableCase] = await Promise.all([this.params.services.attachmentService.update({
        attachmentId: id,
        updatedAttributes: {
          ...queryRestAttributes,
          updated_at: updatedAt,
          updated_by: this.params.user
        },
        options
      }), this.updateCaseUserAndDateSkipRefresh(updatedAt)]);
      await commentableCase.createUpdateCommentUserAction(comment, updateRequest, owner);
      return commentableCase;
    } catch (error) {
      throw (0, _error.createCaseError)({
        message: `Failed to update comment in commentable case, case id: ${this.caseInfo.id}: ${error}`,
        error,
        logger: this.params.logger
      });
    }
  }
  async updateCaseUserAndDateSkipRefresh(date) {
    return this.updateCaseUserAndDate(date, false);
  }
  async updateCaseUserAndDate(date, refresh) {
    try {
      var _updatedCase$version;
      const updatedCase = await this.params.services.caseService.patchCase({
        originalCase: this.caseInfo,
        caseId: this.caseInfo.id,
        updatedAttributes: {
          updated_at: date,
          updated_by: {
            ...this.params.user
          }
        },
        version: this.caseInfo.version,
        refresh
      });
      return this.newObjectWithInfo({
        ...this.caseInfo,
        attributes: {
          ...this.caseInfo.attributes,
          ...updatedCase.attributes
        },
        version: (_updatedCase$version = updatedCase.version) !== null && _updatedCase$version !== void 0 ? _updatedCase$version : this.caseInfo.version
      });
    } catch (error) {
      throw (0, _error.createCaseError)({
        message: `Failed to update commentable case, case id: ${this.caseInfo.id}: ${error}`,
        error,
        logger: this.params.logger
      });
    }
  }
  newObjectWithInfo(caseInfo) {
    return new CaseCommentModel(caseInfo, this.params);
  }
  async createUpdateCommentUserAction(comment, updateRequest, owner) {
    const {
      id,
      version,
      ...queryRestAttributes
    } = updateRequest;
    await this.params.services.userActionService.creator.createUserAction({
      type: _domain.UserActionTypes.comment,
      action: _domain.UserActionActions.update,
      caseId: this.caseInfo.id,
      attachmentId: comment.id,
      payload: {
        attachment: queryRestAttributes
      },
      user: this.params.user,
      owner
    });
  }

  /**
   * Create a new comment on the appropriate case. This updates the case's updated_at and updated_by fields.
   */
  async createComment({
    createdDate,
    commentReq,
    id
  }) {
    try {
      await this.validateCreateCommentRequest([commentReq]);
      const attachmentsWithoutDuplicateAlerts = await this.filterDuplicatedAlerts([{
        ...commentReq,
        id
      }]);
      if (attachmentsWithoutDuplicateAlerts.length === 0) {
        return this;
      }
      const {
        id: commentId,
        ...attachment
      } = attachmentsWithoutDuplicateAlerts[0];
      const references = [...this.buildRefsToCase(), ...this.getCommentReferences(attachment)];
      const [comment, commentableCase] = await Promise.all([this.params.services.attachmentService.create({
        attributes: (0, _utils.transformNewComment)({
          createdDate,
          ...attachment,
          ...this.params.user
        }),
        references,
        id,
        refresh: false
      }), this.updateCaseUserAndDateSkipRefresh(createdDate)]);
      await Promise.all([commentableCase.handleAlertComments([attachment]), this.createCommentUserAction(comment, attachment)]);
      return commentableCase;
    } catch (error) {
      throw (0, _error.createCaseError)({
        message: `Failed creating a comment on a commentable case, case id: ${this.caseInfo.id}: ${error}`,
        error,
        logger: this.params.logger
      });
    }
  }
  async filterDuplicatedAlerts(attachments) {
    /**
     * This function removes the elements in items that exist at the passed in positions.
     */
    const removeItemsByPosition = (items, positionsToRemove) => items.filter((_, itemIndex) => !positionsToRemove.some(position => position === itemIndex));
    const dedupedAlertAttachments = [];
    const idsAlreadySeen = new Set();
    const alertsAttachedToCase = await this.params.services.attachmentService.getter.getAllAlertIds({
      caseId: this.caseInfo.id
    });
    attachments.forEach(attachment => {
      if (!(0, _utils.isCommentRequestTypeAlert)(attachment)) {
        dedupedAlertAttachments.push(attachment);
        return;
      }
      const {
        ids,
        indices
      } = (0, _utils.getIDsAndIndicesAsArrays)(attachment);
      const idPositionsThatAlreadyExistInCase = [];
      ids.forEach((id, index) => {
        if (alertsAttachedToCase.has(id) || idsAlreadySeen.has(id)) {
          idPositionsThatAlreadyExistInCase.push(index);
        }
        idsAlreadySeen.add(id);
      });
      const alertIdsNotAlreadyAttachedToCase = removeItemsByPosition(ids, idPositionsThatAlreadyExistInCase);
      const alertIndicesNotAlreadyAttachedToCase = removeItemsByPosition(indices, idPositionsThatAlreadyExistInCase);
      if (alertIdsNotAlreadyAttachedToCase.length > 0 && alertIdsNotAlreadyAttachedToCase.length === alertIndicesNotAlreadyAttachedToCase.length) {
        dedupedAlertAttachments.push({
          ...attachment,
          alertId: alertIdsNotAlreadyAttachedToCase,
          index: alertIndicesNotAlreadyAttachedToCase
        });
      }
    });
    return dedupedAlertAttachments;
  }
  getAlertAttachments(attachments) {
    return attachments.filter(attachment => attachment.type === _domain.AttachmentType.alert);
  }
  async validateCreateCommentRequest(req) {
    const alertAttachments = this.getAlertAttachments(req);
    const hasAlertsInRequest = alertAttachments.length > 0;
    if (hasAlertsInRequest && this.caseInfo.attributes.status === _domain.CaseStatuses.closed) {
      throw _boom.default.badRequest('Alert cannot be attached to a closed case');
    }
    if (req.some(attachment => attachment.owner !== this.caseInfo.attributes.owner)) {
      throw _boom.default.badRequest('The owner field of the comment must match the case');
    }
    const limitChecker = new _limiter_checker.AttachmentLimitChecker(this.params.services.attachmentService, this.params.fileService, this.caseInfo.id);
    await limitChecker.validate(req);
  }
  buildRefsToCase() {
    return [{
      type: _constants.CASE_SAVED_OBJECT,
      name: `associated-${_constants.CASE_SAVED_OBJECT}`,
      id: this.caseInfo.id
    }];
  }
  getCommentReferences(commentReq) {
    let references = [];
    if (commentReq.type === _domain.AttachmentType.user && commentReq !== null && commentReq !== void 0 && commentReq.comment) {
      const commentStringReferences = (0, _utils.getOrUpdateLensReferences)(this.params.lensEmbeddableFactory, commentReq.comment);
      references = [...references, ...commentStringReferences];
    }
    return references;
  }
  async handleAlertComments(attachments) {
    const alertAttachments = this.getAlertAttachments(attachments);
    const alerts = (0, _utils.getAlertInfoFromComments)(alertAttachments);
    if (alerts.length > 0) {
      await this.params.services.alertsService.ensureAlertsAuthorized({
        alerts
      });
      await this.updateAlertsSchemaWithCaseInfo(alerts);
      if (this.caseInfo.attributes.settings.syncAlerts) {
        await this.updateAlertsStatus(alerts);
      }
    }
  }
  async updateAlertsStatus(alerts) {
    const alertsToUpdate = alerts.map(alert => ({
      ...alert,
      status: this.caseInfo.attributes.status
    }));
    await this.params.services.alertsService.updateAlertsStatus(alertsToUpdate);
  }
  async updateAlertsSchemaWithCaseInfo(alerts) {
    await this.params.services.alertsService.bulkUpdateCases({
      alerts,
      caseIds: [this.caseInfo.id]
    });
  }
  async createCommentUserAction(comment, req) {
    await this.params.services.userActionService.creator.createUserAction({
      type: _domain.UserActionTypes.comment,
      action: _domain.UserActionActions.create,
      caseId: this.caseInfo.id,
      attachmentId: comment.id,
      payload: {
        attachment: req
      },
      user: this.params.user,
      owner: comment.attributes.owner
    });
  }
  async bulkCreateCommentUserAction(attachments) {
    await this.params.services.userActionService.creator.bulkCreateAttachmentCreation({
      caseId: this.caseInfo.id,
      attachments: attachments.map(({
        id,
        ...attachment
      }) => ({
        id,
        owner: attachment.owner,
        attachment
      })),
      user: this.params.user
    });
  }
  formatForEncoding(totalComment) {
    var _this$caseInfo$versio;
    return {
      id: this.caseInfo.id,
      version: (_this$caseInfo$versio = this.caseInfo.version) !== null && _this$caseInfo$versio !== void 0 ? _this$caseInfo$versio : '0',
      totalComment,
      ...this.caseInfo.attributes
    };
  }
  async encodeWithComments() {
    try {
      var _countAlertsForID;
      const comments = await this.params.services.caseService.getAllCaseComments({
        id: this.caseInfo.id,
        options: {
          fields: [],
          page: 1,
          perPage: _constants.MAX_DOCS_PER_PAGE
        }
      });
      const totalAlerts = (_countAlertsForID = (0, _utils.countAlertsForID)({
        comments,
        id: this.caseInfo.id
      })) !== null && _countAlertsForID !== void 0 ? _countAlertsForID : 0;
      const caseResponse = {
        comments: (0, _utils.flattenCommentSavedObjects)(comments.saved_objects),
        totalAlerts,
        ...this.formatForEncoding(comments.total)
      };
      return (0, _runtime_types.decodeOrThrow)(_domain.CaseRt)(caseResponse);
    } catch (error) {
      throw (0, _error.createCaseError)({
        message: `Failed encoding the commentable case, case id: ${this.caseInfo.id}: ${error}`,
        error,
        logger: this.params.logger
      });
    }
  }
  async bulkCreate({
    attachments
  }) {
    try {
      await this.validateCreateCommentRequest(attachments);
      const attachmentWithoutDuplicateAlerts = await this.filterDuplicatedAlerts(attachments);
      if (attachmentWithoutDuplicateAlerts.length === 0) {
        return this;
      }
      const caseReference = this.buildRefsToCase();
      const [newlyCreatedAttachments, commentableCase] = await Promise.all([this.params.services.attachmentService.bulkCreate({
        attachments: attachmentWithoutDuplicateAlerts.map(({
          id,
          ...attachment
        }) => {
          return {
            attributes: (0, _utils.transformNewComment)({
              createdDate: new Date().toISOString(),
              ...attachment,
              ...this.params.user
            }),
            references: [...caseReference, ...this.getCommentReferences(attachment)],
            id
          };
        }),
        refresh: false
      }), this.updateCaseUserAndDateSkipRefresh(new Date().toISOString())]);
      const savedObjectsWithoutErrors = newlyCreatedAttachments.saved_objects.filter(attachment => attachment.error == null);
      const attachmentsWithoutErrors = attachments.filter(attachment => savedObjectsWithoutErrors.some(so => so.id === attachment.id));
      await Promise.all([commentableCase.handleAlertComments(attachmentsWithoutErrors), this.bulkCreateCommentUserAction(attachmentsWithoutErrors)]);
      return commentableCase;
    } catch (error) {
      throw (0, _error.createCaseError)({
        message: `Failed bulk creating attachments on a commentable case, case id: ${this.caseInfo.id}: ${error}`,
        error,
        logger: this.params.logger
      });
    }
  }
}
exports.CaseCommentModel = CaseCommentModel;