import { isString } from 'lodash';
import { templatingVariablesFromUrl } from '../utils';
import { VARIABLE_TYPES } from '../constants';

/**
 * This file exclusively deals with parsing user-defined variables
 * in dashboard yml file.
 *
 * As of 13.0, simple text, advanced text, simple custom and
 * advanced custom variables are supported.
 *
 * In the future iterations, text and query variables will be
 * supported
 *
 */

/**
 * Simple text variable is a string value only.
 * This method parses such variables to a standard format.
 *
 * @param {String|Object} simpleTextVar
 * @returns {Object}
 */
const textSimpleVariableParser = (simpleTextVar) => ({
  type: VARIABLE_TYPES.text,
  label: null,
  value: simpleTextVar,
});

/**
 * Advanced text variable is an object.
 * This method parses such variables to a standard format.
 *
 * @param {Object} advTextVar
 * @returns {Object}
 */
const textAdvancedVariableParser = (advTextVar) => ({
  type: VARIABLE_TYPES.text,
  label: advTextVar.label,
  value: advTextVar.options.default_value,
});

/**
 * Normalize simple and advanced custom variable options to a standard
 * format
 * @param {Object} custom variable option
 * @returns {Object} normalized custom variable options
 */
const normalizeVariableValues = ({ default: defaultOpt = false, text, value = null }) => ({
  default: defaultOpt,
  text: text || value,
  value,
});

/**
 * Custom advanced variables are rendered as dropdown elements in the dashboard
 * header. This method parses advanced custom variables.
 *
 * The default value is the option with default set to true or the first option
 * if none of the options have default prop true.
 *
 * @param {Object} advVariable advanced custom variable
 * @returns {Object}
 */
const customAdvancedVariableParser = (advVariable) => {
  const values = (advVariable?.options?.values ?? []).map(normalizeVariableValues);
  const defaultValue = values.find((opt) => opt.default === true) || values[0];
  return {
    type: VARIABLE_TYPES.custom,
    label: advVariable.label,
    options: {
      values,
    },
    value: defaultValue?.value || null,
  };
};

/**
 * Simple custom variables have an array of values.
 * This method parses such variables options to a standard format.
 *
 * @param {String} opt option from simple custom variable
 * @returns {Object}
 */
export const parseSimpleCustomValues = (opt) => ({ text: opt, value: opt });

/**
 * Custom simple variables are rendered as dropdown elements in the dashboard
 * header. This method parses simple custom variables.
 *
 * Simple custom variables do not have labels so its set to null here.
 *
 * The default value is set to the first option as the user cannot
 * set a default value for this format
 *
 * @param {Array} customVariable array of options
 * @returns {Object}
 */
const customSimpleVariableParser = (simpleVar) => {
  const values = (simpleVar || []).map(parseSimpleCustomValues);
  return {
    type: VARIABLE_TYPES.custom,
    label: null,
    value: values[0].value || null,
    options: {
      values: values.map(normalizeVariableValues),
    },
  };
};

const metricLabelValuesVariableParser = ({ label, options = {} }) => ({
  type: VARIABLE_TYPES.metric_label_values,
  label,
  value: null,
  options: {
    prometheusEndpointPath: options.prometheus_endpoint_path || '',
    label: options.label || null,
    values: [], // values are initially empty
  },
});

/**
 * Utility method to determine if a custom variable is
 * simple or not. If its not simple, it is advanced.
 *
 * @param {Array|Object} customVar Array if simple, object if advanced
 * @returns {Boolean} true if simple, false if advanced
 */
const isSimpleCustomVariable = (customVar) => Array.isArray(customVar);

/**
 * This method returns a parser based on the type of the variable.
 * Currently, the supported variables are simple custom and
 * advanced custom only. In the future, this method will support
 * text and query variables.
 *
 * @param {Array|Object} variable
 * @return {Function} parser method
 */
const getVariableParser = (variable) => {
  if (isString(variable)) {
    return textSimpleVariableParser;
  } else if (isSimpleCustomVariable(variable)) {
    return customSimpleVariableParser;
  } else if (variable.type === VARIABLE_TYPES.text) {
    return textAdvancedVariableParser;
  } else if (variable.type === VARIABLE_TYPES.custom) {
    return customAdvancedVariableParser;
  } else if (variable.type === VARIABLE_TYPES.metric_label_values) {
    return metricLabelValuesVariableParser;
  }
  return () => null;
};

/**
 * This method parses the templating property in the dashboard yml file.
 * The templating property has variables that are rendered as input elements
 * for the user to edit. The values from input elements are relayed to
 * backend and eventually Prometheus API.
 *
 * @param {Object} templating variables from the dashboard yml file
 * @returns {array} An array of variables to display as inputs
 */
export const parseTemplatingVariables = (ymlVariables = {}) =>
  Object.entries(ymlVariables).reduce((acc, [name, ymlVariable]) => {
    // get the parser
    const parser = getVariableParser(ymlVariable);
    // parse the variable
    const variable = parser(ymlVariable);
    // for simple custom variable label is null and it should be
    // replace with key instead
    if (variable) {
      acc.push({
        ...variable,
        name,
        label: variable.label || name,
      });
    }
    return acc;
  }, []);

/**
 * Custom variables are defined in the dashboard yml file
 * and their values can be passed through the URL.
 *
 * On component load, this method merges variables data
 * from the yml file with URL data to store in the Vuex store.
 * Not all params coming from the URL need to be stored. Only
 * the ones that have a corresponding variable defined in the
 * yml file.
 *
 * This ensures that there is always a single source of truth
 * for variables
 *
 * This method can be improved further. See the below issue
 * https://gitlab.com/gitlab-org/gitlab/-/issues/217713
 *
 * @param {array} parsedYmlVariables - template variables from yml file
 * @returns {Object}
 */
export const mergeURLVariables = (parsedYmlVariables = []) => {
  const varsFromURL = templatingVariablesFromUrl();
  parsedYmlVariables.forEach((variable) => {
    const { name } = variable;
    if (Object.prototype.hasOwnProperty.call(varsFromURL, name)) {
      Object.assign(variable, { value: varsFromURL[name] });
    }
  });
  return parsedYmlVariables;
};

/**
 * Converts series data to options that can be added to a
 * variable. Series data is returned from the Prometheus API
 * `/api/v1/series`.
 *
 * Finds a `label` in the series data, so it can be used as
 * a filter.
 *
 * For example, for the arguments:
 *
 * {
 *   "label": "job"
 *   "data" : [
 *     {
 *       "__name__" : "up",
 *       "job" : "prometheus",
 *       "instance" : "localhost:9090"
 *     },
 *     {
 *       "__name__" : "up",
 *       "job" : "node",
 *       "instance" : "localhost:9091"
 *     },
 *     {
 *       "__name__" : "process_start_time_seconds",
 *       "job" : "prometheus",
 *       "instance" : "localhost:9090"
 *     }
 *   ]
 * }
 *
 * It returns all the different "job" values:
 *
 * [
 *   {
 *     "label": "node",
 *     "value": "node"
 *   },
 *   {
 *     "label": "prometheus",
 *     "value": "prometheus"
 *   }
 * ]
 *
 * @param {options} options object
 * @param {options.seriesLabel} name of the searched series label
 * @param {options.data} series data from the series API
 * @return {array} Options objects with the shape `{ label, value }`
 *
 * @see https://prometheus.io/docs/prometheus/latest/querying/api/#finding-series-by-label-matchers
 */
export const optionsFromSeriesData = ({ label, data = [] }) => {
  const optionsSet = data.reduce((set, seriesObject) => {
    // Use `new Set` to deduplicate options
    if (seriesObject[label]) {
      set.add(seriesObject[label]);
    }
    return set;
  }, new Set());

  return [...optionsSet].map(parseSimpleCustomValues);
};
