import { concat, filter, find, forEach, indexOf, intersection, isObject, map, without, keys as _keys } from 'lodash';
import { dateTime } from '@grafana/data';

export default class ResponseParser {
  constructor(private results: any) {}

  parseQueryResult() {
    let data: any = [];
    let columns: any = [];
    for (let i = 0; i < this.results.length; i++) {
      if (this.results[i].query.raw) {
        const xaxis = this.results[i].query.xaxis;
        const yaxises = this.results[i].query.yaxis;
        const spliton = this.results[i].query.spliton;
        columns = this.results[i].result.Tables[0].Columns;
        const rows = this.results[i].result.Tables[0].Rows;
        data = concat(data, this.parseRawQueryResultRow(this.results[i].query, columns, rows, xaxis, yaxises, spliton));
      } else {
        const value = this.results[i].result.value;
        const alias = this.results[i].query.alias;
        data = concat(data, this.parseQueryResultRow(this.results[i].query, value, alias));
      }
    }
    return data;
  }

  parseRawQueryResultRow(query: any, columns: any, rows: any, xaxis: string, yaxises: string, spliton: string) {
    const data: any[] = [];
    const columnsForDropdown = map(columns, (column) => ({ text: column.ColumnName, value: column.ColumnName }));

    const xaxisColumn = columns.findIndex((column: any) => column.ColumnName === xaxis);
    const yaxisesSplit = yaxises.split(',');
    const yaxisColumns: any = {};
    forEach(yaxisesSplit, (yaxis) => {
      yaxisColumns[yaxis] = columns.findIndex((column: any) => column.ColumnName === yaxis);
    });
    const splitonColumn = columns.findIndex((column: any) => column.ColumnName === spliton);
    const convertTimestamp = xaxis === 'timestamp';

    forEach(rows, (row) => {
      forEach(yaxisColumns, (yaxisColumn, yaxisName) => {
        const bucket =
          splitonColumn === -1
            ? ResponseParser.findOrCreateBucket(data, yaxisName)
            : ResponseParser.findOrCreateBucket(data, row[splitonColumn]);
        const epoch = convertTimestamp ? ResponseParser.dateTimeToEpoch(row[xaxisColumn]) : row[xaxisColumn];
        bucket.datapoints.push([row[yaxisColumn], epoch]);
        bucket.refId = query.refId;
        bucket.query = query.query;
        bucket.columnsForDropdown = columnsForDropdown;
      });
    });

    return data;
  }

  parseQueryResultRow(query: any, value: any, alias: string) {
    const data: any[] = [];

    if (ResponseParser.isSingleValue(value)) {
      const metricName = ResponseParser.getMetricFieldKey(value);
      const aggField = ResponseParser.getKeyForAggregationField(value[metricName]);
      const epoch = ResponseParser.dateTimeToEpoch(value.end);
      data.push({
        target: metricName,
        datapoints: [[value[metricName][aggField], epoch]],
        refId: query.refId,
        query: query.query,
      });
      return data;
    }

    const groupedBy = ResponseParser.hasSegmentsField(value.segments[0]);
    if (!groupedBy) {
      const metricName = ResponseParser.getMetricFieldKey(value.segments[0]);
      const dataTarget = ResponseParser.findOrCreateBucket(data, metricName);

      for (let i = 0; i < value.segments.length; i++) {
        const epoch = ResponseParser.dateTimeToEpoch(value.segments[i].end);
        const aggField: string = ResponseParser.getKeyForAggregationField(value.segments[i][metricName]);

        dataTarget.datapoints.push([value.segments[i][metricName][aggField], epoch]);
      }
      dataTarget.refId = query.refId;
      dataTarget.query = query.query;
    } else {
      for (let i = 0; i < value.segments.length; i++) {
        const epoch = ResponseParser.dateTimeToEpoch(value.segments[i].end);

        for (let j = 0; j < value.segments[i].segments.length; j++) {
          const metricName = ResponseParser.getMetricFieldKey(value.segments[i].segments[j]);
          const aggField = ResponseParser.getKeyForAggregationField(value.segments[i].segments[j][metricName]);
          const target = this.getTargetName(value.segments[i].segments[j], alias);

          const bucket = ResponseParser.findOrCreateBucket(data, target);
          bucket.datapoints.push([value.segments[i].segments[j][metricName][aggField], epoch]);
          bucket.refId = query.refId;
          bucket.meta = {
            query: query.query,
          };
        }
      }
    }

    return data;
  }

  getTargetName(segment: { [x: string]: string }, alias: string) {
    let metric = '';
    let segmentName = '';
    let segmentValue = '';
    for (const prop in segment) {
      if (isObject(segment[prop])) {
        metric = prop;
      } else {
        segmentName = prop;
        segmentValue = segment[prop];
      }
    }

    if (alias) {
      const regex = /\{\{([\s\S]+?)\}\}/g;
      return alias.replace(regex, (match, g1, g2) => {
        const group = g1 || g2;

        if (group === 'metric') {
          return metric;
        } else if (group === 'groupbyname') {
          return segmentName;
        } else if (group === 'groupbyvalue') {
          return segmentValue;
        }

        return match;
      });
    }

    return metric + `{${segmentName}="${segmentValue}"}`;
  }

  static isSingleValue(value: any) {
    return !ResponseParser.hasSegmentsField(value);
  }

  static findOrCreateBucket(data: any[], target: string) {
    let dataTarget: any = find(data, ['target', target]);
    if (!dataTarget) {
      dataTarget = { target: target, datapoints: [] };
      data.push(dataTarget);
    }

    return dataTarget;
  }

  static hasSegmentsField(obj: any) {
    const keys = _keys(obj);
    return indexOf(keys, 'segments') > -1;
  }

  static getMetricFieldKey(segment: { [x: string]: any }) {
    const keys = _keys(segment);

    return filter(without(keys, 'start', 'end'), (key) => {
      return isObject(segment[key]);
    })[0];
  }

  static getKeyForAggregationField(dataObj: any): string {
    const keys = _keys(dataObj);
    return intersection(keys, ['sum', 'avg', 'min', 'max', 'count', 'unique'])[0];
  }

  static dateTimeToEpoch(dateTimeValue: any) {
    return dateTime(dateTimeValue).valueOf();
  }

  static parseMetricNames(result: { metrics: any }) {
    const keys = _keys(result.metrics);

    return ResponseParser.toTextValueList(keys);
  }

  parseMetadata(metricName: string) {
    const metric = this.results.metrics[metricName];

    if (!metric) {
      throw Error('No data found for metric: ' + metricName);
    }

    return {
      primaryAggType: metric.defaultAggregation,
      supportedAggTypes: metric.supportedAggregations,
      supportedGroupBy: metric.supportedGroupBy.all,
    };
  }

  parseGroupBys() {
    return ResponseParser.toTextValueList(this.results.supportedGroupBy);
  }

  parseQuerySchema() {
    const result: any = {
      Type: 'AppInsights',
      Tables: {},
    };
    if (this.results && this.results && this.results.Tables) {
      for (let i = 0; i < this.results.Tables[0].Rows.length; i++) {
        const column = this.results.Tables[0].Rows[i];
        const columnTable = column[0];
        const columnName = column[1];
        const columnType = column[2];
        if (result.Tables[columnTable]) {
          result.Tables[columnTable].OrderedColumns.push({ Name: columnName, Type: columnType });
        } else {
          result.Tables[columnTable] = {
            Name: columnTable,
            OrderedColumns: [{ Name: columnName, Type: columnType }],
          };
        }
      }
    }
    return result;
  }

  static toTextValueList(values: any) {
    const list: any[] = [];
    for (let i = 0; i < values.length; i++) {
      list.push({
        text: values[i],
        value: values[i],
      });
    }
    return list;
  }
}
