"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.AlertService = void 0;
var _pMap = _interopRequireDefault(require("p-map"));
var _lodash = require("lodash");
var _technical_rule_data_field_names = require("@kbn/rule-registry-plugin/common/technical_rule_data_field_names");
var _domain = require("../../../common/types/domain");
var _constants = require("../../../common/constants");
var _error = require("../../common/error");
/*
 * 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.
 */

class AlertService {
  constructor(scopedClusterClient, logger, alertsClient) {
    this.scopedClusterClient = scopedClusterClient;
    this.logger = logger;
    this.alertsClient = alertsClient;
  }
  async executeAggregations({
    aggregationBuilders,
    alerts
  }) {
    try {
      const {
        ids,
        indices
      } = AlertService.getUniqueIdsIndices(alerts);
      const builtAggs = aggregationBuilders.reduce((acc, agg) => {
        return Object.assign(acc, agg.build());
      }, {});
      const res = await this.scopedClusterClient.search({
        index: indices,
        ignore_unavailable: true,
        query: {
          ids: {
            values: ids
          }
        },
        size: 0,
        aggregations: builtAggs
      });
      return res.aggregations;
    } catch (error) {
      const aggregationNames = aggregationBuilders.map(agg => agg.getName());
      throw (0, _error.createCaseError)({
        message: `Failed to execute aggregations [${aggregationNames.join(',')}]: ${error}`,
        error,
        logger: this.logger
      });
    }
  }
  static getUniqueIdsIndices(alerts) {
    const {
      ids,
      indices
    } = alerts.reduce((acc, alert) => {
      acc.ids.add(alert.id);
      acc.indices.add(alert.index);
      return acc;
    }, {
      ids: new Set(),
      indices: new Set()
    });
    return {
      ids: Array.from(ids),
      indices: Array.from(indices)
    };
  }
  async updateAlertsStatus(alerts) {
    try {
      const bucketedAlerts = this.bucketAlertsByIndexAndStatus(alerts);
      const indexBuckets = Array.from(bucketedAlerts.entries());
      await (0, _pMap.default)(indexBuckets, async indexBucket => this.updateByQuery(indexBucket), {
        concurrency: _constants.MAX_CONCURRENT_SEARCHES
      });
    } catch (error) {
      throw (0, _error.createCaseError)({
        message: `Failed to update alert status ids: ${JSON.stringify(alerts)}: ${error}`,
        error,
        logger: this.logger
      });
    }
  }
  bucketAlertsByIndexAndStatus(alerts) {
    return alerts.reduce((acc, alert) => {
      // skip any alerts that are empty
      if (AlertService.isEmptyAlert(alert)) {
        return acc;
      }
      const translatedAlert = {
        ...alert,
        status: this.translateStatus(alert)
      };
      const statusToAlertId = acc.get(translatedAlert.index);

      // if we haven't seen the index before
      if (!statusToAlertId) {
        // add a new index in the parent map, with an entry for the status the alert set to pointing
        // to an initial array of only the current alert
        acc.set(translatedAlert.index, createStatusToAlertMap(translatedAlert));
      } else {
        // We had the index in the map so check to see if we have a bucket for the
        // status, if not add a new status entry with the alert, if so update the status entry
        // with the alert
        updateIndexEntryWithStatus(statusToAlertId, translatedAlert);
      }
      return acc;
    }, new Map());
  }
  static isEmptyAlert(alert) {
    return (0, _lodash.isEmpty)(alert.id) || (0, _lodash.isEmpty)(alert.index);
  }
  translateStatus(alert) {
    const translatedStatuses = {
      [_domain.CaseStatuses.open]: 'open',
      [_domain.CaseStatuses['in-progress']]: 'acknowledged',
      [_domain.CaseStatuses.closed]: 'closed'
    };
    const translatedStatus = translatedStatuses[alert.status];
    if (!translatedStatus) {
      this.logger.error(`Unable to translate case status ${alert.status} during alert update: ${JSON.stringify(alert)}`);
    }
    return translatedStatus !== null && translatedStatus !== void 0 ? translatedStatus : 'open';
  }
  async updateByQuery([index, statusToAlertMap]) {
    const statusBuckets = Array.from(statusToAlertMap);
    return Promise.all(
    // this will create three update by query calls one for each of the three statuses
    statusBuckets.map(([status, translatedAlerts]) => this.scopedClusterClient.updateByQuery({
      index,
      conflicts: 'abort',
      body: {
        script: {
          source: `if (ctx._source['${_technical_rule_data_field_names.ALERT_WORKFLOW_STATUS}'] != null) {
                ctx._source['${_technical_rule_data_field_names.ALERT_WORKFLOW_STATUS}'] = '${status}'
              }
              if (ctx._source.signal != null && ctx._source.signal.status != null) {
                ctx._source.signal.status = '${status}'
              }`,
          lang: 'painless'
        },
        // the query here will contain all the ids that have the same status for the same index
        // being updated
        query: {
          ids: {
            values: translatedAlerts.map(({
              id
            }) => id)
          }
        }
      },
      ignore_unavailable: true
    })));
  }
  getNonEmptyAlerts(alerts) {
    return alerts.filter(alert => !AlertService.isEmptyAlert(alert));
  }
  async getAlerts(alertsInfo) {
    try {
      const docs = alertsInfo.filter(alert => !AlertService.isEmptyAlert(alert)).slice(0, _constants.MAX_ALERTS_PER_CASE).map(alert => ({
        _id: alert.id,
        _index: alert.index
      }));
      if (docs.length <= 0) {
        return;
      }
      const results = await this.scopedClusterClient.mget({
        body: {
          docs
        }
      });
      return results;
    } catch (error) {
      throw (0, _error.createCaseError)({
        message: `Failed to retrieve alerts ids: ${JSON.stringify(alertsInfo)}: ${error}`,
        error,
        logger: this.logger
      });
    }
  }
  async bulkUpdateCases({
    alerts,
    caseIds
  }) {
    try {
      const nonEmptyAlerts = this.getNonEmptyAlerts(alerts);
      if (nonEmptyAlerts.length <= 0) {
        return;
      }
      await this.alertsClient.bulkUpdateCases({
        alerts: nonEmptyAlerts,
        caseIds
      });
    } catch (error) {
      throw (0, _error.createCaseError)({
        message: `Failed to add case info to alerts for caseIds ${caseIds}: ${error}`,
        error,
        logger: this.logger
      });
    }
  }
  async removeCaseIdFromAlerts({
    alerts,
    caseId
  }) {
    try {
      const nonEmptyAlerts = this.getNonEmptyAlerts(alerts);
      if (nonEmptyAlerts.length <= 0) {
        return;
      }
      await this.alertsClient.removeCaseIdFromAlerts({
        alerts: nonEmptyAlerts,
        caseId
      });
    } catch (error) {
      /**
       * We intentionally do not throw an error.
       * Users should be able to remove alerts from a case even
       * in the event of an error produced by the alerts client
       */
      this.logger.error(`Failed removing case ${caseId} from alerts: ${error}`);
    }
  }
  async removeCaseIdsFromAllAlerts({
    caseIds
  }) {
    try {
      if (caseIds.length <= 0) {
        return;
      }
      await this.alertsClient.removeCaseIdsFromAllAlerts({
        caseIds
      });
    } catch (error) {
      /**
       * We intentionally do not throw an error.
       * Users should be able to remove alerts from cases even
       * in the event of an error produced by the alerts client
       */
      this.logger.error(`Failed removing cases ${caseIds} for all alerts: ${error}`);
    }
  }
  async ensureAlertsAuthorized({
    alerts
  }) {
    try {
      const nonEmptyAlerts = this.getNonEmptyAlerts(alerts);
      if (nonEmptyAlerts.length <= 0) {
        return;
      }
      await this.alertsClient.ensureAllAlertsAuthorizedRead({
        alerts: nonEmptyAlerts
      });
    } catch (error) {
      throw (0, _error.createCaseError)({
        message: `Failed to authorize alerts: ${error}`,
        error,
        logger: this.logger
      });
    }
  }
}
exports.AlertService = AlertService;
function createStatusToAlertMap(alert) {
  return new Map([[alert.status, [alert]]]);
}
function updateIndexEntryWithStatus(statusToAlerts, alert) {
  const statusBucket = statusToAlerts.get(alert.status);
  if (!statusBucket) {
    statusToAlerts.set(alert.status, [alert]);
  } else {
    statusBucket.push(alert);
  }
}