"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.buildComponentTemplates = buildComponentTemplates;
exports.ensureAliasHasWriteIndex = ensureAliasHasWriteIndex;
exports.ensureComponentTemplate = ensureComponentTemplate;
exports.ensureDefaultComponentTemplates = ensureDefaultComponentTemplates;
exports.getAllTemplateRefs = getAllTemplateRefs;
exports.installComponentAndIndexTemplateForDataStream = installComponentAndIndexTemplateForDataStream;
exports.prepareTemplate = prepareTemplate;
exports.prepareToInstallTemplates = void 0;
var _lodash = require("lodash");
var _boom = _interopRequireDefault(require("@hapi/boom"));
var _types = require("../../../../types");
var _services = require("../../../../../common/services");
var _field = require("../../fields/field");
var _archive = require("../../archive");
var _constants = require("../../../../constants");
var _meta2 = require("../meta");
var _retry = require("../retry");
var _experimental_datastream_features_helper = require("../../../experimental_datastream_features_helper");
var _app_context = require("../../../app_context");
var _template = require("./template");
var _default_settings = require("./default_settings");
/*
 * 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.
 */

const FLEET_COMPONENT_TEMPLATE_NAMES = _constants.FLEET_COMPONENT_TEMPLATES.map(tmpl => tmpl.name);
const prepareToInstallTemplates = (installablePackage, paths, esReferences, experimentalDataStreamFeatures = [], onlyForDataStreams) => {
  // remove package installation's references to index templates
  const assetsToRemove = esReferences.filter(({
    type
  }) => type === _types.ElasticsearchAssetType.indexTemplate || type === _types.ElasticsearchAssetType.componentTemplate);

  // build templates per data stream from yml files
  const dataStreams = onlyForDataStreams || installablePackage.data_streams;
  if (!dataStreams) return {
    assetsToAdd: [],
    assetsToRemove,
    install: () => Promise.resolve([])
  };
  const templates = dataStreams.map(dataStream => {
    const experimentalDataStreamFeature = experimentalDataStreamFeatures.find(datastreamFeature => datastreamFeature.data_stream === (0, _services.getRegistryDataStreamAssetBaseName)(dataStream));
    return prepareTemplate({
      pkg: installablePackage,
      dataStream,
      experimentalDataStreamFeature
    });
  });
  const assetsToAdd = getAllTemplateRefs(templates.map(template => template.indexTemplate));
  return {
    assetsToAdd,
    assetsToRemove,
    install: async (esClient, logger) => {
      // install any pre-built index template assets,
      // atm, this is only the base package's global index templates
      // Install component templates first, as they are used by the index templates
      await installPreBuiltComponentTemplates(paths, esClient, logger);
      await installPreBuiltTemplates(paths, esClient, logger);
      await Promise.all(templates.map(template => installComponentAndIndexTemplateForDataStream({
        esClient,
        logger,
        componentTemplates: template.componentTemplates,
        indexTemplate: template.indexTemplate
      })));
      return templates.map(template => template.indexTemplate);
    }
  };
};
exports.prepareToInstallTemplates = prepareToInstallTemplates;
const installPreBuiltTemplates = async (paths, esClient, logger) => {
  const templatePaths = paths.filter(path => isTemplate(path));
  const templateInstallPromises = templatePaths.map(async path => {
    const {
      file
    } = (0, _archive.getPathParts)(path);
    const templateName = file.substr(0, file.lastIndexOf('.'));
    const content = JSON.parse((0, _archive.getAsset)(path).toString('utf8'));
    const esClientParams = {
      name: templateName,
      body: content
    };
    const esClientRequestOptions = {
      ignore: [404]
    };
    if (content.hasOwnProperty('template') || content.hasOwnProperty('composed_of')) {
      // Template is v2
      return (0, _retry.retryTransientEsErrors)(() => esClient.indices.putIndexTemplate(esClientParams, esClientRequestOptions), {
        logger
      });
    } else {
      // template is V1
      return (0, _retry.retryTransientEsErrors)(() => esClient.indices.putTemplate(esClientParams, esClientRequestOptions), {
        logger
      });
    }
  });
  try {
    return await Promise.all(templateInstallPromises);
  } catch (e) {
    throw new _boom.default.Boom(`Error installing prebuilt index templates ${e.message}`, {
      statusCode: 400
    });
  }
};
const installPreBuiltComponentTemplates = async (paths, esClient, logger) => {
  const templatePaths = paths.filter(path => isComponentTemplate(path));
  const templateInstallPromises = templatePaths.map(async path => {
    const {
      file
    } = (0, _archive.getPathParts)(path);
    const templateName = file.substr(0, file.lastIndexOf('.'));
    const content = JSON.parse((0, _archive.getAsset)(path).toString('utf8'));
    const esClientParams = {
      name: templateName,
      body: content
    };
    return (0, _retry.retryTransientEsErrors)(() => esClient.cluster.putComponentTemplate(esClientParams, {
      ignore: [404]
    }), {
      logger
    });
  });
  try {
    return await Promise.all(templateInstallPromises);
  } catch (e) {
    throw new _boom.default.Boom(`Error installing prebuilt component templates ${e.message}`, {
      statusCode: 400
    });
  }
};
const isTemplate = path => {
  const pathParts = (0, _archive.getPathParts)(path);
  return pathParts.type === _types.ElasticsearchAssetType.indexTemplate;
};
const isComponentTemplate = path => {
  const pathParts = (0, _archive.getPathParts)(path);
  return pathParts.type === _types.ElasticsearchAssetType.componentTemplate;
};

/**
 * installComponentAndIndexTemplateForDataStream installs one template for each data stream
 *
 * The template is currently loaded with the pkgkey-package-data_stream
 */

async function installComponentAndIndexTemplateForDataStream({
  esClient,
  logger,
  componentTemplates,
  indexTemplate
}) {
  // update index template first in case TSDS was removed, so that it does not become invalid
  await updateIndexTemplateIfTsdsDisabled({
    esClient,
    logger,
    indexTemplate
  });
  await installDataStreamComponentTemplates({
    esClient,
    logger,
    componentTemplates
  });
  await installTemplate({
    esClient,
    logger,
    template: indexTemplate
  });
}
async function updateIndexTemplateIfTsdsDisabled({
  esClient,
  logger,
  indexTemplate
}) {
  try {
    var _existingIndexTemplat, _existingIndexTemplat2, _existingIndexTemplat3, _existingIndexTemplat4, _existingIndexTemplat5;
    const existingIndexTemplate = await esClient.indices.getIndexTemplate({
      name: indexTemplate.templateName
    });
    if (((_existingIndexTemplat = existingIndexTemplate.index_templates) === null || _existingIndexTemplat === void 0 ? void 0 : (_existingIndexTemplat2 = _existingIndexTemplat[0]) === null || _existingIndexTemplat2 === void 0 ? void 0 : (_existingIndexTemplat3 = _existingIndexTemplat2.index_template.template) === null || _existingIndexTemplat3 === void 0 ? void 0 : (_existingIndexTemplat4 = _existingIndexTemplat3.settings) === null || _existingIndexTemplat4 === void 0 ? void 0 : (_existingIndexTemplat5 = _existingIndexTemplat4.index) === null || _existingIndexTemplat5 === void 0 ? void 0 : _existingIndexTemplat5.mode) === 'time_series' && indexTemplate.indexTemplate.template.settings.index.mode !== 'time_series') {
      await installTemplate({
        esClient,
        logger,
        template: indexTemplate
      });
    }
  } catch (e) {
    if (e.statusCode === 404) {
      logger.debug(`Index template ${indexTemplate.templateName} does not exist, skipping time_series check`);
    } else {
      logger.warn(`Error while trying to install index template before component template: ${e.message}`);
    }
  }
}
function putComponentTemplate(esClient, logger, params) {
  const {
    name,
    body,
    create = false
  } = params;
  return {
    clusterPromise: (0, _retry.retryTransientEsErrors)(() => esClient.cluster.putComponentTemplate(
    // @ts-expect-error lifecycle is not yet supported here
    {
      name,
      body,
      create
    }, {
      ignore: [404]
    }), {
      logger
    }),
    name
  };
}
const isUserSettingsTemplate = name => name.endsWith(_constants.USER_SETTINGS_TEMPLATE_SUFFIX);
function buildComponentTemplates(params) {
  var _registryElasticsearc, _registryElasticsearc2, _indexTemplateMapping, _mappings$dynamic_tem, _indexTemplateMapping2, _indexTemplateMapping3, _templateSettings$ind, _templateSettings$ind2, _templateSettings$ind3;
  const {
    templateName,
    registryElasticsearch,
    packageName,
    defaultSettings,
    mappings,
    pipelineName,
    experimentalDataStreamFeature,
    lifecycle
  } = params;
  const packageTemplateName = `${templateName}${_constants.PACKAGE_TEMPLATE_SUFFIX}`;
  const userSettingsTemplateName = `${templateName}${_constants.USER_SETTINGS_TEMPLATE_SUFFIX}`;
  const templatesMap = {};
  const _meta = (0, _meta2.getESAssetMetadata)({
    packageName
  });
  const indexTemplateSettings = (_registryElasticsearc = registryElasticsearch === null || registryElasticsearch === void 0 ? void 0 : registryElasticsearch['index_template.settings']) !== null && _registryElasticsearc !== void 0 ? _registryElasticsearc : {};
  const templateSettings = (0, _lodash.merge)(defaultSettings, indexTemplateSettings);
  const indexTemplateMappings = (_registryElasticsearc2 = registryElasticsearch === null || registryElasticsearch === void 0 ? void 0 : registryElasticsearch['index_template.mappings']) !== null && _registryElasticsearc2 !== void 0 ? _registryElasticsearc2 : {};
  const isDocValueOnlyNumericEnabled = (experimentalDataStreamFeature === null || experimentalDataStreamFeature === void 0 ? void 0 : experimentalDataStreamFeature.features.doc_value_only_numeric) === true;
  const isDocValueOnlyOtherEnabled = (experimentalDataStreamFeature === null || experimentalDataStreamFeature === void 0 ? void 0 : experimentalDataStreamFeature.features.doc_value_only_other) === true;
  if (isDocValueOnlyNumericEnabled || isDocValueOnlyOtherEnabled) {
    (0, _experimental_datastream_features_helper.forEachMappings)(mappings.properties, (mappingProp, name) => (0, _experimental_datastream_features_helper.applyDocOnlyValueToMapping)(mappingProp, name, experimentalDataStreamFeature, isDocValueOnlyNumericEnabled, isDocValueOnlyOtherEnabled));
  }
  const mappingsProperties = (0, _lodash.merge)(mappings.properties, (_indexTemplateMapping = indexTemplateMappings.properties) !== null && _indexTemplateMapping !== void 0 ? _indexTemplateMapping : {});
  const mappingsDynamicTemplates = (0, _lodash.uniqBy)((0, _lodash.concat)((_mappings$dynamic_tem = mappings.dynamic_templates) !== null && _mappings$dynamic_tem !== void 0 ? _mappings$dynamic_tem : [], (_indexTemplateMapping2 = indexTemplateMappings.dynamic_templates) !== null && _indexTemplateMapping2 !== void 0 ? _indexTemplateMapping2 : []), dynampingTemplate => Object.keys(dynampingTemplate)[0]);
  const mappingsRuntimeFields = (0, _lodash.merge)(mappings.runtime, (_indexTemplateMapping3 = indexTemplateMappings.runtime) !== null && _indexTemplateMapping3 !== void 0 ? _indexTemplateMapping3 : {});
  const isTimeSeriesEnabledByDefault = (registryElasticsearch === null || registryElasticsearch === void 0 ? void 0 : registryElasticsearch.index_mode) === 'time_series';
  const isSyntheticSourceEnabledByDefault = (registryElasticsearch === null || registryElasticsearch === void 0 ? void 0 : registryElasticsearch.source_mode) === 'synthetic';
  const sourceModeSynthetic = (experimentalDataStreamFeature === null || experimentalDataStreamFeature === void 0 ? void 0 : experimentalDataStreamFeature.features.synthetic_source) !== false && ((experimentalDataStreamFeature === null || experimentalDataStreamFeature === void 0 ? void 0 : experimentalDataStreamFeature.features.synthetic_source) === true || isSyntheticSourceEnabledByDefault || isTimeSeriesEnabledByDefault);
  templatesMap[packageTemplateName] = {
    template: {
      settings: {
        ...templateSettings,
        index: {
          ...templateSettings.index,
          ...(pipelineName ? {
            default_pipeline: pipelineName
          } : {}),
          mapping: {
            ...((_templateSettings$ind = templateSettings.index) === null || _templateSettings$ind === void 0 ? void 0 : _templateSettings$ind.mapping),
            total_fields: {
              ...((_templateSettings$ind2 = templateSettings.index) === null || _templateSettings$ind2 === void 0 ? void 0 : (_templateSettings$ind3 = _templateSettings$ind2.mapping) === null || _templateSettings$ind3 === void 0 ? void 0 : _templateSettings$ind3.total_fields),
              limit: '10000'
            }
          }
        }
      },
      mappings: {
        properties: mappingsProperties,
        ...(Object.keys(mappingsRuntimeFields).length > 0 ? {
          runtime: mappingsRuntimeFields
        } : {}),
        dynamic_templates: mappingsDynamicTemplates.length ? mappingsDynamicTemplates : undefined,
        ...(0, _lodash.omit)(indexTemplateMappings, 'properties', 'dynamic_templates', '_source', 'runtime'),
        ...(indexTemplateMappings !== null && indexTemplateMappings !== void 0 && indexTemplateMappings._source || sourceModeSynthetic ? {
          _source: {
            ...(indexTemplateMappings === null || indexTemplateMappings === void 0 ? void 0 : indexTemplateMappings._source),
            ...(sourceModeSynthetic ? {
              mode: 'synthetic'
            } : {})
          }
        } : {})
      },
      ...(lifecycle ? {
        lifecycle
      } : {})
    },
    _meta
  };

  // return empty/stub template
  templatesMap[userSettingsTemplateName] = {
    template: {
      settings: {}
    },
    _meta
  };
  return templatesMap;
}
async function installDataStreamComponentTemplates({
  esClient,
  logger,
  componentTemplates
}) {
  await Promise.all(Object.entries(componentTemplates).map(async ([name, body]) => {
    if (isUserSettingsTemplate(name)) {
      try {
        // Attempt to create custom component templates, ignore if they already exist
        const {
          clusterPromise
        } = putComponentTemplate(esClient, logger, {
          body,
          name,
          create: true
        });
        return await clusterPromise;
      } catch (e) {
        var _e$body, _e$body$error;
        if ((e === null || e === void 0 ? void 0 : e.statusCode) === 400 && (_e$body = e.body) !== null && _e$body !== void 0 && (_e$body$error = _e$body.error) !== null && _e$body$error !== void 0 && _e$body$error.reason.includes('already exists')) {
          // ignore
        } else {
          throw e;
        }
      }
    } else {
      const {
        clusterPromise
      } = putComponentTemplate(esClient, logger, {
        body,
        name
      });
      return clusterPromise;
    }
  }));
}
async function ensureDefaultComponentTemplates(esClient, logger) {
  return Promise.all(_constants.FLEET_COMPONENT_TEMPLATES.map(({
    name,
    body
  }) => ensureComponentTemplate(esClient, logger, name, body)));
}
async function ensureComponentTemplate(esClient, logger, name, body) {
  var _getTemplateRes$compo;
  const getTemplateRes = await (0, _retry.retryTransientEsErrors)(() => esClient.cluster.getComponentTemplate({
    name
  }, {
    ignore: [404]
  }), {
    logger
  });
  const existingTemplate = getTemplateRes === null || getTemplateRes === void 0 ? void 0 : (_getTemplateRes$compo = getTemplateRes.component_templates) === null || _getTemplateRes$compo === void 0 ? void 0 : _getTemplateRes$compo[0];
  if (!existingTemplate) {
    await putComponentTemplate(esClient, logger, {
      name,
      body
    }).clusterPromise;
  }
  return {
    isCreated: !existingTemplate
  };
}
async function ensureAliasHasWriteIndex(opts) {
  const {
    esClient,
    logger,
    aliasName,
    writeIndexName,
    body
  } = opts;
  const existingIndex = await (0, _retry.retryTransientEsErrors)(() => esClient.indices.exists({
    index: [aliasName]
  }, {
    ignore: [404]
  }), {
    logger
  });
  if (!existingIndex) {
    logger.info(`Creating write index [${writeIndexName}], alias [${aliasName}]`);
    await (0, _retry.retryTransientEsErrors)(() => esClient.indices.create({
      index: writeIndexName,
      ...body
    }, {
      ignore: [404]
    }), {
      logger
    });
  }
}
function prepareTemplate({
  pkg,
  dataStream,
  experimentalDataStreamFeature
}) {
  var _dataStream$elasticse, _appContextService$ge, _appContextService$ge2, _appContextService$ge3;
  const {
    name: packageName,
    version: packageVersion
  } = pkg;
  const fields = (0, _field.loadFieldsFromYaml)(pkg, dataStream.path);
  const isIndexModeTimeSeries = ((_dataStream$elasticse = dataStream.elasticsearch) === null || _dataStream$elasticse === void 0 ? void 0 : _dataStream$elasticse.index_mode) === 'time_series' || (experimentalDataStreamFeature === null || experimentalDataStreamFeature === void 0 ? void 0 : experimentalDataStreamFeature.features.tsdb);
  const validFields = (0, _field.processFields)(fields);
  const mappings = (0, _template.generateMappings)(validFields, isIndexModeTimeSeries);
  const templateName = (0, _template.generateTemplateName)(dataStream);
  const templateIndexPattern = (0, _template.generateTemplateIndexPattern)(dataStream);
  const templatePriority = (0, _template.getTemplatePriority)(dataStream);
  const isILMPolicyDisabled = (_appContextService$ge = (_appContextService$ge2 = _app_context.appContextService.getConfig()) === null || _appContextService$ge2 === void 0 ? void 0 : (_appContextService$ge3 = _appContextService$ge2.internal) === null || _appContextService$ge3 === void 0 ? void 0 : _appContextService$ge3.disableILMPolicies) !== null && _appContextService$ge !== void 0 ? _appContextService$ge : false;
  const lifecyle = isILMPolicyDisabled && dataStream.lifecycle ? dataStream.lifecycle : undefined;
  const pipelineName = (0, _services.getPipelineNameForDatastream)({
    dataStream,
    packageVersion
  });
  const defaultSettings = (0, _default_settings.buildDefaultSettings)({
    templateName,
    packageName,
    fields: validFields,
    type: dataStream.type,
    ilmPolicy: dataStream.ilm_policy
  });
  const componentTemplates = buildComponentTemplates({
    defaultSettings,
    mappings,
    packageName,
    templateName,
    pipelineName,
    registryElasticsearch: dataStream.elasticsearch,
    experimentalDataStreamFeature,
    lifecycle: lifecyle
  });
  const template = (0, _template.getTemplate)({
    templateIndexPattern,
    packageName,
    composedOfTemplates: Object.keys(componentTemplates),
    templatePriority,
    hidden: dataStream.hidden,
    registryElasticsearch: dataStream.elasticsearch,
    mappings,
    isIndexModeTimeSeries
  });
  return {
    componentTemplates,
    indexTemplate: {
      templateName,
      indexTemplate: template
    }
  };
}
async function installTemplate({
  esClient,
  logger,
  template
}) {
  // TODO: Check return values for errors
  const esClientParams = {
    name: template.templateName,
    body: template.indexTemplate
  };
  await (0, _retry.retryTransientEsErrors)(() => esClient.indices.putIndexTemplate(esClientParams, {
    ignore: [404]
  }), {
    logger
  });
}
function getAllTemplateRefs(installedTemplates) {
  return installedTemplates.flatMap(installedTemplate => {
    const indexTemplates = [{
      id: installedTemplate.templateName,
      type: _types.ElasticsearchAssetType.indexTemplate
    }];
    const componentTemplates = installedTemplate.indexTemplate.composed_of
    // Filter global component template shared between integrations
    .filter(componentTemplateId => !FLEET_COMPONENT_TEMPLATE_NAMES.includes(componentTemplateId)).map(componentTemplateId => ({
      id: componentTemplateId,
      type: _types.ElasticsearchAssetType.componentTemplate
    }));
    return indexTemplates.concat(componentTemplates);
  });
}